Logging für Cloud-Anwendungen

the next big thing  –  4 Kommentare

Die Entwicklung von Anwendungen für die Cloud folgt anderen Richtlinien als die für serverseitige Webanwendungen. Welche Auswirkungen hat das auf das Schreiben von Lognachrichten?

Herkömmliche serverseitige Webanwendungen schreiben ihre Lognachrichten in der Regel in eine oder mehrere Dateien im lokalen Dateisystem. Für nahezu jede Sprache und Plattform gibt es entsprechende Frameworks und Bibliotheken, die das unterstützen. In der Cloud macht das Vorgehen jedoch wenig Sinn, da Anwendungen in dem Fall damit rechnen müssen, jederzeit von einem Server auf einen anderen verschoben zu werden. Der Abschuss und Neustart der Anwendung im laufenden Betrieb, einschließlich des Wechsels des zugrunde liegenden Servers, sind hier die Regel, nicht die Ausnahme.

Das bedeutet auch, dass das lokale Dateisystem und dessen Inhalt jederzeit verloren gehen können. Es ist daher nicht als persistenter Speicher anzusehen, sondern eher als eine Art temporärer Cache. Hinzu kommt, dass Anwendungen in der Cloud in der Regel nicht nur auf einem, sondern auf vielen Servern gleichzeitig betrieben werden, sodass man zunächst sämtliche Logdateien einsammeln müsste, um einen Überblick über den aktuellen Stand einer Anwendung zu erhalten.

Als Ausweg bietet sich der Einsatz eines dedizierten Servers an, der für das Einsammeln von Lognachrichten gedacht ist. Im einfachsten Fall ist das eine Datenbank, in die die einzelnen Prozesse hineinschreiben.

Ideal ist das jedoch nicht. Denn damit es funktioniert, muss eine Anwendung Code enthalten, um Lognachrichten an den Logserver zu senden. Das ist allerdings eine Aufgabe, die nicht in den Kernbereich der Anwendung fällt – schließlich wird keine Anwendung mit dem primären Ziel geschrieben, Lognachrichten zu generieren und zu speichern.

Stattdessen handelt es sich um einen Seitenaspekt, der jedoch in jeder einzelnen Anwendung erneut eingebaut, gepflegt, dokumentiert und getestet werden muss. Jeder solcher Aspekt verschlechtert jedoch die Wartbarkeit des eigentlich fachlich interessanten Codes, weshalb das Schreiben der Lognachrichten im Idealfall außerhalb der Anwendung erfolgen würde.

Genau das schlägt das Konzept der 12-Faktor-Anwendungen vor, das Lognachrichten als Ströme von Events bezeichnet. Die grundlegende Idee der 12-Faktor-Anwendungen ist an der Stelle, Lognachrichten schlichtweg in die Standardausgabe zu schreiben:

"A twelve-factor app never concerns itself with routing or storage of its output stream. It should not attempt to write to or manage logfiles. Instead, each running process writes its event stream, unbuffered, to stdout."

Das hat zwei Vorteile: Zur Entwicklungszeit kann man die Ausgabe der Lognachrichten auf der Kommandozeile verfolgen. Während des Produktiveinsatzes fängt die Systemumgebung die Ausgaben ab und kümmert sich um das Weiterleiten der Datenströme an die entsprechende Infrastruktur. Das alles erfolgt auf eine für die Anwendung vollständig transparente Weise:

"These archival destinations are not visible to or configurable by the app, and instead are completely managed by the execution environment."

Um Lognachrichten gemäß diesem Paradigma mit Node.js zu schreiben, lässt sich das Modul flaschenpost verwenden. Es ist von vornherein für das Logging in Cloud-Anwendungen entwickelt worden und folgt den Prinzipien der 12-Faktor-Anwendungen. Dazu gilt es zunächst, das Modul zu installieren. Das erfolgt auf dem üblichen Weg mit Hilfe von npm:

$ npm install flaschenpost

Anschließend lässt es sich in einer Anwendung wie folgt integrieren:

const flaschenpost = require('flaschenpost');

Um eine Lognachricht zu schreiben, muss man zunächst einen Logger erzeugen. Ein solches Objekt repräsentiert eine Datei, und flaschenpost kümmert sich automatisch darum, die Lognachrichten mit den entsprechenden Metadaten wie dem Dateinamen anzureichern:

const logger = flaschenpost.getLogger();

Danach lassen sich Lognachrichten schreiben. Dazu ist die Funktion des Loggers aufzurufen, die dem gewünschten Loglevel (fatal, error, warn, info oder debug) entspricht:

logger.info('Application started.');

Um Lognachrichten um eigene Metadaten anzureichern, lässt sich der entsprechenden Funktion als zweiter Parameter ein beliebiges Objekt übergeben:

logger.info('Application started.', { port: 3000 });

Die Lognachrichten sendet flaschenpost daraufhin wie erwähnt an die Standardausgabe.

Um das weitere Verarbeiten der Lognachrichten möglichst einfach zu gestalten, werden die Lognachrichten als JSON-Objekte formatiert. Jede Zeile der Ausgabe enthält ein JSON-Objekt, sodass sich insgesamt das JSON-Lines-Format ergibt. So praktisch das für die automatisierte Verarbeitung ist, so unpraktisch ist es für den Menschen während der Entwicklungszeit.

Daher ermittelt flaschenpost automatisch, ob eine Anwendung von Hand auf der Kommandozeile gestartet wurde. Falls ja, wird eine menschenlesbare Ausgabe anstelle der JSON-Ausgabe verwendet. Auf dem Weg erhält man stets das zur jeweiligen Umgebung passende Ausgabeformat.

Bei Bedarf lässt sich auch ein bestimmtes Ausgabeformat erzwingen. Dazu ist die Umgebungsvariable FLASCHENPOST_FORMATTER auf den gewünschten Wert zu setzen, beispielsweise json oder human. Interessant kann dabei vor allem auch die Option js: sein, die die Definition eines eigenen Formats ermöglicht.

Um Lognachrichten zu verarbeiten, gilt es, diese gemäß dem Konzept der 12-Faktor-Anwendungen auf für die Anwendung transparentem Weg aufzuzeichnen und zu behandeln. Im einfachsten Fall kann das bedeuten, die Ausgabe in eine Datei umzulenken:

$ node app.js > log.json

Wahrscheinlicher ist jedoch, dass man die Lognachrichten an einen zentralen Server schicken will, beispielsweise eine Datenbank wie Elasticsearch oder ein auf die Verarbeitung von Lognachrichten spezialisiertes System wie Graylog.

Das Schöne an dem Ansatz ist, dass die Art der Weiterverarbeitung nun außerhalb der Anwendung konfiguriert wird. Für die Anwendung ist das vollständig transparent, was den Code nicht nur einfacher macht, sondern auch dem Single-Responsibility-Prinzip (SRP) entgegenkommt.

tl;dr: Das Konzept der 12-Faktor-Anwendungen schlägt vor, dass Cloud-Anwendungen ihre Lognachrichten lediglich auf die Standardausgabe schreiben. Das Modul flaschenpost stellt ein Logging-Framework zur Verfügung, was den Ansatz nativ verfolgt.