Reden wir über Protokollierung, sollen wir? Arnold hier drüben, der einen riesigen Baumstamm trägt, fühlt sich wie eine angemessene Einführung in diesen Artikel an, in dem wir über beliebte Knoten sprechen werden.js-Protokollierungs-Frameworks.
Wenn Sie eine langlebige Anwendung schreiben, ist eine detaillierte Protokollierung von größter Bedeutung, um Probleme zu erkennen und zu debuggen. Ohne Protokolle hätten Sie nur wenige Möglichkeiten zu sagen, wie sich Ihre Anwendung verhält, gibt es Fehler, wie ist die Leistung, macht sie überhaupt etwas oder fällt sie einfach über jede andere Anfrage, wenn Sie es nicht betrachten.
Anforderungen
Lassen Sie uns einige Anforderungen identifizieren, mit denen wir die Frameworks gegeneinander ausspielen können. Einige dieser Anforderungen sind ziemlich trivial, andere sind nicht so sehr.
- Zeitstempel für jede Protokollzeile. Dieser ist ziemlich selbsterklärend – Sie sollten in der Lage sein zu erkennen, wann jeder Protokolleintrag stattgefunden hat.
- Das Format sollte sowohl für Menschen als auch für Maschinen leicht verdaulich sein.
- Ermöglicht mehrere konfigurierbare Zielströme. Wenn jedoch ein Fehler auftritt, schreiben Sie in dieselbe Datei und dann in die Fehlerdatei und senden gleichzeitig eine E-Mail.
Basierend auf diesen Anforderungen (und der Beliebtheit) gibt es zwei Protokollierungsframeworks für Node.besonders sehenswert:
- Bunyan von Trent Mick.
- Winston ist Teil des Flatiron-Frameworks und wird von nodejitstu gesponsert.
Konsole
Bevor wir zu Bunyan und Winston kommen, schauen wir uns unseren alten Freund console
an. Die rudimentärste Art der Protokollierung, die Sie durchführen können, ist die Verwendung der Methoden console.log
und console.error
. Das ist besser als nichts, aber kaum die beste Lösung. Die Konsole schreibt in STDOUT bzw. Es gibt eine sehr interessante Einschränkung zu wissen, wenn es um console
Methoden im Knoten geht.js.
Die Konsolenfunktionen sind synchron, wenn das Ziel ein Terminal oder eine Datei ist (um verlorene Nachrichten bei vorzeitigem Beenden zu vermeiden) und asynchron, wenn es sich um eine Pipe handelt (um eine Blockierung für längere Zeit zu vermeiden).
Das heißt, im folgenden Beispiel ist stdout nicht blockierend, während stderr blockiert:
$ node script.js 2> error.log | tee info.log
Dies ist im Grunde ein „roll your own“ -Protokollierungsansatz. Es ist vollständig manuell, Sie müssen sich Ihr eigenes Format einfallen lassen und im Grunde alles selbst verwalten. Dies ist zeitaufwändig, fehleranfällig und Sie möchten sich wahrscheinlich stattdessen auf Ihre Anwendungsfunktionen konzentrieren. In Anbetracht der Tatsache, dass es Open-Source-Protokollierungsbibliotheken gibt, die aktiv gepflegt werden, ist dies die Mühe nicht wert, wenn Sie sich auf die Bereitstellung von Funktionen konzentrieren möchten.
Winston
Einer der beliebtesten Knoten.js Logging Frameworks ist Winston. Es ist eine einfache und universelle Protokollbibliothek mit Unterstützung für mehrere Transporte (ein Transport in Winstons Welt ist im Wesentlichen ein Speichergerät, z. B. wo Ihre Protokolle gespeichert werden). Für jede Instanz eines Winston-Loggers können mehrere Transporte mit unterschiedlichen Protokollierungsebenen konfiguriert werden.
Installation
npm install winston
Verwendung
Die grundlegendste Verwendung von Winston besteht darin, die Standardinstanz aufzurufen, die aus dem winston
Modul exportiert wird.
var winston = require('winston');winston.log('info', 'Hello distributed log files!');winston.info('Hello again distributed logs');
Das obige ist das gleiche wie:
Beide Beispiele erzeugen die folgende Ausgabe:
info: Hello distributed log files!info: Hello again distributed logs
Formatierung
Ich persönlich bin ein wenig verwirrt über die fehlende Details im Standardformatierer. Es gibt keinen Zeitstempel, keinen Maschinennamen oder keine Prozess-ID und das Ausgabeformat ist für die maschinelle Analyse leicht geeignet. Allerdings können Sie alle Informationen mit nur ein wenig zusätzlicher Arbeit selbst herausholen.
winston.info('Hello world!', {timestamp: Date.now(), pid: process.pid});
Erzeugt die folgende Ausgabe, die informativer ist, aber immer noch nicht sehr gut für das maschinelle Parsen geeignet ist.
info: Hello world! timestamp=1402286804314, pid=80481
Schließlich bietet die log
Methode die gleichen String-Interpolationsmethoden wie util.format
, zum Beispiel:
winston.log('info', 'test message %d', 123);
Transporter
Winston könnte über Konstruktoroptionen oder exponierte Methoden konfiguriert werden, die auf der GitHub-Seite sehr ausführlich dokumentiert sind. Der größte Teil der Konfiguration dreht sich normalerweise um verschiedene Transporte. Out of the box Winston kommt mit Konsole und dateibasierte Transporte und wenn Sie einen Blick auf npmjs.org sie werden sehen, dass es Community-Module für so ziemlich alles Erdenkliche gibt, von MongoDB bis hin zu kommerziellen Plattformen von Drittanbietern.
Einer der bemerkenswertesten Transporter ist meiner Meinung nach winston-irc von Nathan Zadoks, mit dem Sie Fehler im IRC-Kanal Ihres Teams protokollieren können. Ich kann sehen, dass dies sehr praktisch ist.
winston.add(require('winston-irc'), { host: 'irc.somewhere.net', nick: 'logger', pass: 'hunter2', channels: { '#logs': true, 'sysadmin': }});
Mehrere Logger
Sobald Ihre Anwendung zu wachsen beginnt, werden Sie wahrscheinlich mehrere Logger mit unterschiedlichen Konfigurationen haben wollen, wobei jeder Logger für einen anderen Funktionsbereich (oder eine andere Kategorie) verantwortlich ist. Winston unterstützt dies auf zwei Arten: durch winston.loggers
und Instanzen von winston.Container
. Tatsächlich ist winston.loggers
nur eine vordefinierte Instanz von winston.Container
:
winston.loggers.add('category1', {console: { ... }, file: { ... }});winston.loggers.add('category2', {irc: { ... }, file: { ... }});
Nun, da Ihre Logger konfiguriert sind, können Sie Winston in einer beliebigen Datei in Ihrer Anwendung anfordern und auf diese vorkonfigurierten Logger zugreifen:
var category1 = winston.loggers.get('category1');category1.info('logging from your IoC container-based logger');
Mehr
Dies ist die grundlegendste , aber es gibt einige andere Eigenschaften, am bemerkenswertesten:
- Profiling
- String interpolation
- Abfragen und Streaming
- Umgang mit Ausnahmen
Bunyan
Illustration von Brendan Corris
Bunyan von Trent Mick ist ein weiteres Logging-Framework, das meiner Meinung nach in Betracht gezogen werden sollte. Bunyan verfolgt einen etwas anderen Ansatz bei der Protokollierung als Winston und hat es sich zur Aufgabe gemacht, strukturierte, maschinenlesbare Protokolle als erstklassige Bürger bereitzustellen. Infolgedessen ist ein Protokolldatensatz von Bunyan eine Zeile JSON.stringify
Ausgabe mit einigen allgemeinen Namen für die erforderlichen und allgemeinen Felder für einen Protokolldatensatz.
Installation
npm install bunyan
Verwendung
var bunyan = require('bunyan');var log = bunyan.createLogger({name: 'myapp'});log.info('hi');log.warn({lang: 'fr'}, 'au revoir');
Dies erzeugt die folgende Ausgabe:
Wie Sie sehen können, ist Bunyan nicht sehr menschenfreundlich die meisten modernen Protokollierungssysteme verstehen das JSON-Format jedoch nativ, was bedeutet, dass hier wenig zu tun ist, um die Protokolle an anderer Stelle zur Speicherung und Verarbeitung einzuspeisen. Standardmäßig sind in jeder Nachricht einige Metadaten enthalten, z. B. Zeitstempel, Prozess-ID, Hostname und Anwendungsname.
Natürlich finden wir Menschen das nicht sehr verdaulich und um das zu adressieren, gibt es ein bunyan
CLI-Tool, das JSON über STDIN aufnimmt. Hier ist das gleiche Beispiel, das durch bunyan
geleitet wird:
node example.js | bunyan
Erzeugt die folgende Ausgabe:
INFO: myapp/13372 on pwony-2: hi WARN: myapp/13372 on pwony-2: au revoir (lang=fr)
Der Hauptvorteil hier ist, dass Sie nichts für die Entwicklungsumgebung neu konfigurieren müssen, alles, was Sie tun müssen, ist Pipe zu bunyan
. Weitere Informationen zum CLI-Tool finden Sie auf der GitHub-Seite.
JSON
Einer der Hauptunterschiede zwischen Bunyan und Winston ist, dass Bunyan sehr gut funktioniert, wenn Sie komplexe Kontexte und Objekte protokollieren möchten. Schauen wir uns diese Zeile und ihre Ausgabe aus dem obigen Beispiel an:
Sie können sehen, dass {lang: 'fr'}
mit dem Hauptprotokollobjekt zusammengeführt wurde und au revoir
wurde msg
. Stellen Sie sich nun Folgendes vor:
log.info(user, 'registered');log.info({user: user}, 'registered');
Was erzeugt:
Oder wenn durch bunyan
:
Die Schönheit dieses Ansatzes wird deutlich, wenn Sie sich untergeordnete Logger ansehen.
Untergeordnete Logger
Bunyan hat ein Konzept von untergeordneten Loggern, mit dem Sie einen Logger für eine Unterkomponente Ihrer Anwendung spezialisieren können, z. so erstellen Sie einen neuen Logger mit zusätzlichen gebundenen Feldern, die in seine Protokolldatensätze aufgenommen werden. Ein untergeordneter Logger wird mit log.child(...)
erstellt. Dies ist unglaublich praktisch, wenn Sie Bereichslogger für verschiedene Komponenten in Ihrem System, Anforderungen oder einfach nur Funktionsaufrufe haben möchten. Schauen wir uns einen Code an.
Stellen Sie sich vor, Sie möchten die Anforderungs-ID durch alle Protokollzeilen für eine bestimmte Anforderung tragen, damit Sie sie alle zusammenbinden können.
Der req.log
Logger wird immer seinen Kontext an die log.child()
Funktion übergeben und mit allen nachfolgenden Aufrufen zusammenführen, so dass die Ausgabe ungefähr so aussehen würde:
{"name":"myapp","hostname":"pwony-2","pid":14837,"level":30,"reqId":"XXXX-XX-XXXX","user":"[email protected]","time":"2014-05-26T18:27:43.530Z","v":0}
Serializer
Zwei Probleme treten auf, wenn Bunyan versucht, ganze Objekte zu stringifizieren:
- Zirkelverweise. Winston ist hier etwas schlauer und erkennt Zirkelverweise, wenn sie auftreten (die Ergebnisausgabe
$ref=$
ist jedoch nicht sehr nützlich). - Unerwünschtes Rauschen. Es fühlt sich für mich so an, als wäre es viel einfacher, sich daran zu gewöhnen, einfach alles in das Protokoll zu werfen, da Objekte erstklassig sind.
Um mit beiden umzugehen, hat Bunyan ein Konzept von Serializer , bei dem es sich im Grunde genommen um Transformationsfunktionen handelt, mit denen Sie häufig übergebene Objekte nur auf die Felder beschränken können, an denen Sie interessiert sind:
Wenn Sie nun versuchen, req
Objekt würde nur die drei Felder enthalten, an denen wir interessiert sind.
Streams
Streams in Bunyan sind dasselbe wie Transporter in Winston – es ist eine Möglichkeit, Ihre Protokolle zur Anzeige und Speicherung an einen anderen Ort zu senden. Bunyan verwendet eine beschreibbare Stream-Schnittstelle mit einigen zusätzlichen Attributen. Eine Bunyan-Logger-Instanz hat einen oder mehrere Streams und wird mit der streams
-Option angegeben:
var log = bunyan.createLogger({ name: "foo", streams: });
Mehr
Hier sind einige weitere bemerkenswerte Dinge, die Sie in Bunyan erkunden können:
- Laufzeitprotokoll-Snooping über Dtrace-Unterstützung
- Log record fields
Was soll ich wählen?
Winston und Bunyan sind beide sehr ausgereifte und etablierte Logging-Frameworks und in Bezug auf die Funktionen sehr gleichwertig. Winston hat viel Community-Unterstützung mit verschiedenen Protokollierungsmodulen. Bunyan macht es einfach, Protokolle zu analysieren, überlässt dies jedoch dem Benutzer (im Allgemeinen funktioniert syslog drain hier ziemlich gut). Ich denke, alles hängt von den Vorlieben ab und davon, wie einfach es ist, sich in Ihren Stack zu integrieren.
- Was kommt zur nächsten Node-Version? Lesen Sie von den Autoren selbst über acht aufregende neue Node v0.12-Funktionen und wie Sie diese optimal nutzen können.
- Bereit, APIs in Node zu entwickeln.js und sie mit Ihren Daten verbinden? Schauen Sie sich den Knoten an.js LoopBack API Rahmen. Wir haben es Ihnen leicht gemacht, entweder lokal oder in Ihrer bevorzugten Cloud mit einer einfachen npm-Installation zu beginnen.
- Benötigen Sie Schulungen und Zertifizierungen für Node? Erfahren Sie mehr über die privaten und offenen Optionen, die StrongLoop bietet.