
Wie oft haben Sie logger.info('ServiceName.methodName.')
Und logger.info('ServiceName.methodName -> done.')
Für jede Methode Ihres Dienstes geschrieben, die Sie protokollieren wollten? Möchten Sie, dass es automatisiert wird und in Ihrer gesamten App dieselbe konstante Signatur aufweist? Wenn dem so ist, sind wir uns sehr ähnlich, wir haben zu oft den gleichen Schmerz erlitten, und jetzt könnten wir endlich versuchen, ihn zu lösen. Zusammen. Meine Damen und Herren, lassen Sie mich vorstellen ... Klassenlogger !
Ingenieure sind oft Perfektionisten. Perfektionisten bis zum Äußersten. Wir mögen ordentliche Abstraktionen. Wir mögen sauberen Code. Wir sehen Schönheit in künstlichen Sprachen, die andere Menschen nicht einmal lesen können. Wir stellen gerne kleine digitale Universen her und leben nach den von uns festgelegten Regeln. Wir mögen das alles wahrscheinlich, weil wir sehr faul sind. Nein, wir haben keine Angst vor der Arbeit, aber wir hassen es, Arbeiten zu erledigen, die automatisiert werden können.
Nachdem wir nur einige Tausend Zeilen Protokollierungscode geschrieben haben, entwickeln wir normalerweise bestimmte Muster, um zu standardisieren, was wir protokollieren möchten. Wir müssen diese Muster jedoch noch manuell anwenden. Die Kernidee von Class-Logger besteht daher darin, eine deklarative, hoch konfigurierbare, standardisierte Methode zum Protokollieren von Nachrichten vor und nach der Ausführung einer Klassenmethode bereitzustellen.
Schnellstart
Lassen Sie uns loslegen und sehen, wie der eigentliche Code aussieht.
import { LogClass, Log } from 'class-logger' @LogClass() class ServiceCats { @Log() eat(food: string) { return 'purr' } }
Dieser Dienst wird dreimal protokolliert:
- Bei seiner Erstellung mit einer Liste von Argumenten an den Konstruktor übergeben.
- Vor dem
eat
wird mit einer Liste seiner Argumente ausgeführt. - After
eat
wird mit einer Liste seiner Argumente und seines Ergebnisses ausgeführt.
In Worten des Codes:
// Logs before the actual call to the constructor // `ServiceCats.construct. Args: [].` const serviceCats = new ServiceCats() // Logs before the actual call to `eat` // `ServiceCats.eat. Args: [milk].` serviceCats.eat('milk') // Logs after the actual call to `eat` // `ServiceCats.eat -> done. Args: [milk]. Res: purr.`
Live-Demo .
Was könnten wir noch protokollieren? Hier ist die vollständige Liste der Ereignisse:
- Vor dem Klassenbau.
- Vor synchronen und asynchronen statischen und nicht statischen Methoden und Funktionseigenschaften.
- Nach synchronen und asynchronen statischen und nicht statischen Methoden und Funktionseigenschaften.
- Fehler von synchronen und asynchronen statischen und nicht statischen Methoden und Funktionseigenschaften.
Funktionale Eigenschaft ist eine Pfeilfunktion, die einer Eigenschaft zugewiesen ist ( class ServiceCats { private meow = () => null }
).
Anpassung an unsere Bedürfnisse
So weit so gut, aber uns wurde "anpassbar" versprochen, oder? Wie können wir es also optimieren?
Der Klassenlogger bietet drei Ebenen hierarchischer Konfiguration:
Bei jedem Methodenaufruf werden alle drei ausgewertet und von oben nach unten zusammengeführt. Es gibt eine vernünftige globale Standardkonfiguration, sodass Sie die Bibliothek ohne Konfiguration verwenden können.
Globale Konfiguration
Es ist die App-weite Konfiguration. Kann mit setConfig
Aufruf setConfig
.
import { setConfig } from 'class-logger' setConfig({ log: console.info, })
Klassenkonfiguration
Es wirkt sich auf jede Methode Ihrer Klasse aus. Es könnte die globale Konfiguration überschreiben.
import { LogClass } from 'class-logger' setConfig({ log: console.info, }) @LogClass({ // It overrides global config for this service log: console.debug, }) class ServiceCats {}
Methodenkonfiguration
Es betrifft nur die Methode selbst. Überschreibt die Klassenkonfiguration und damit die globale Konfiguration.
import { LogClass } from 'class-logger' setConfig({ log: console.info, }) @LogClass({ // It overrides global config for this service log: console.debug, }) class ServiceCats { private energy = 100 @Log({ // It overrides class config for this method only log: console.warn, }) eat(food: string) { return 'purr' } // This method stil uses `console.debug` provided by class config sleep() { this.energy += 100 } }
Live-Demo
Konfigurationsoptionen
Nun, wir haben gelernt, wie man die Standardeinstellungen ändert, aber es würde nicht schaden, zu beschreiben, was konfiguriert werden muss, oder?
Hier finden Sie viele nützliche Konfigurationsüberschreibungen .
Hier ist der Link zur Oberfläche des Konfigurationsobjekts, falls Sie TypeScript besser sprechen als Englisch :)
Das Konfigurationsobjekt hat folgende Eigenschaften:
log
Diese Funktion protokolliert die endgültige formatierte Nachricht. Es wird verwendet, um diese Ereignisse zu protokollieren:
- Vor dem Klassenbau.
- Vor synchronen und asynchronen statischen und nicht statischen Methoden und Funktionseigenschaften.
- Nach synchronen und asynchronen statischen und nicht statischen Methoden und Funktionseigenschaften.
Standard: console.log
logError
Diese Funktion protokolliert die endgültige formatierte Fehlermeldung. Es wird verwendet, um dieses einzige Ereignis zu protokollieren:
- Fehler von synchronen und asynchronen statischen und nicht statischen Methoden und Funktionseigenschaften.
Standard: console.error
Es ist ein Objekt mit zwei Methoden: start
und end
. Es formatiert Protokolldaten in die endgültige Zeichenfolge.
start
für diese Ereignisse:
- Vor dem Klassenbau.
- Vor synchronen und asynchronen statischen und nicht statischen Methoden und Funktionseigenschaften.
end
formatiert Nachrichten für diese Ereignisse:
- Nach synchronen und asynchronen statischen und nicht statischen Methoden und Funktionseigenschaften.
- Fehler von synchronen und asynchronen statischen und nicht statischen Methoden und Funktionseigenschaften.
Standard: new ClassLoggerFormatterService()
einschließen
Die Konfiguration dessen, was in der Nachricht enthalten sein soll.
args
Es kann entweder ein Boolescher Wert oder ein Objekt sein.
Wenn es sich um einen Booleschen Args: [milk]
, wird festgelegt, ob die Liste der Argumente (denken Args: [milk]
daran, dass Args: [milk]
?) In beide Nachrichten (Start (vor der Konstruktion und vor dem Methodenaufruf) und Ende (nach dem Methodenaufruf, Fehlermethodenaufruf)) eingefügt werden soll.
Wenn es sich um ein Objekt handelt, sollte es zwei boolesche Eigenschaften haben: start
und end
. start
schließt die Liste der Argumente für Startnachrichten ein / aus, end
macht dasselbe für Endnachrichten.
Standard: true
konstruieren
Eine boolesche Flageinstellung, ob die Klassenkonstruktion protokolliert werden soll oder nicht.
Standard: true
Ergebnis
Eine weitere boolesche Flag-Einstellung, ob ein Rückgabewert eines Methodenaufrufs oder ein von ihm ausgelöster Fehler enthalten sein soll. Res: purr
du dich an Res: purr
? Wenn Sie dieses Flag auf false
wird kein Res: purr
.
Standard: true
classInstance
Wieder entweder ein Boolescher Wert oder ein Objekt.
Wenn Sie es aktivieren, wird den Protokollen eine stringifizierte Darstellung Ihrer Klasseninstanz hinzugefügt. Mit anderen Worten, wenn Ihre Klasseninstanz einige Eigenschaften hat, werden diese in eine JSON-Zeichenfolge konvertiert und der Protokollnachricht hinzugefügt.
Nicht alle Eigenschaften werden hinzugefügt. Der Klassenlogger folgt dieser Logik:
- Nehmen Sie eigene (Nicht-Prototyp-) Eigenschaften einer Instanz.
- Warum? Es ist ein seltener Fall, wenn sich Ihr Prototyp dynamisch ändert, daher ist es kaum sinnvoll, ihn zu protokollieren.
- Löschen Sie alle mit
function
.
- Warum? Die meisten
function
sind nur unveränderliche Pfeilfunktionen, die anstelle regulärer Klassenmethoden verwendet werden, um this
Kontext beizubehalten. Es macht nicht viel Sinn, Ihre Protokolle mit festgelegten Körpern dieser Funktionen aufzublähen.
- Lassen Sie alle Objekte fallen, die keine einfachen Objekte sind.
- Welche Objekte sind einfache?
ClassLoggerFormatterService
betrachtet ein Objekt als einfaches Objekt, wenn sein Prototyp streng gleich Object.prototype
. - Warum? Oft schließen wir Instanzen anderer Klassen als Eigenschaften ein (fügen Sie sie als Abhängigkeiten ein). Unsere Protokolle würden extrem fett werden, wenn wir stringifizierte Versionen dieser Abhängigkeiten einschließen würden.
- Stringifizieren Sie, was noch übrig ist.
class ServiceA {} @LogClass({ include: { classInstance: true, }, }) class Test { private serviceA = new ServiceA() private prop1 = 42 private prop2 = { test: 42 } private method1 = () => null @Log() public method2() { return 42 } } // Logs to the console before the class' construction: // 'Test.construct. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}.' const test = new Test() // Logs to the console before the method call: // 'Test.method2. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}.' test.method2() // Logs to the console after the method call: // 'Test.method2 -> done. Args: []. Class instance: {"prop1":42,"prop2":{"test":42}}. Res: 42.'
Standard: false
Was ist, wenn Ihnen die Gesamtidee gefällt, Sie aber möchten, dass Ihre Nachrichten anders aussehen? Sie können die vollständige Kontrolle über die Formatierung übernehmen, indem Sie Ihren eigenen benutzerdefinierten Formatierer übergeben.
Sie können Ihren eigenen Formatierer von Grund auf neu schreiben. Total. Wir werden diese Option hier jedoch nicht behandeln (wenn Sie wirklich daran interessiert sind, lesen Sie den Abschnitt "Formatierung" in der README-Datei).
Am schnellsten und wahrscheinlich einfachsten ist es, einen integrierten Standardformatierer - ClassLoggerFormatterService
- in ClassLoggerFormatterService
.
ClassLoggerFormatterService
verfügt über diese geschützten Methoden, die als Bausteine für die endgültige Nachricht dienen:
base
- Gibt den Klassennamen mit dem Methodennamen zurück. Beispiel:
ServiceCats.eat
.
operation
- Gibt
-> done
oder -> error
, je nachdem, ob eine Methode erfolgreich oder fehlerhaft ausgeführt wurde.
args
- Gibt eine stringifizierte Liste von Argumenten zurück. Beispiel:.
. Args: [milk]
. Es verwendet Fast-Safe-Stringify für Objekte unter der Haube.
classInstance
- Gibt eine stringifizierte Klasseninstanz zurück. Beispiel:.
. Class instance: {"prop1":42,"prop2":{"test":42}}
. Wenn Sie die Klasseninstanz einschließen, diese jedoch nicht verfügbar ist (so ist dies für statische Methoden und die Klassenkonstruktion), wird N/A
result
- Gibt ein stringifiziertes Ergebnis der Ausführung zurück (auch wenn es ein Fehler war). Verwendet Fast-Safe-Stringify, um Objekte zu serialisieren. Ein String-Fehler besteht aus den folgenden Eigenschaften:
- Name der Klasse (Funktion), mit der der Fehler erstellt wurde (
error.constructor.name
). - Fehlercode (
error.code
). - Fehlermeldung (
error.message
). error.name
( error.name
).error.stack
( error.stack
).
final
Die Startnachricht besteht aus:
base
args
classInstance
final
Die Endnachricht besteht aus:
base
operation
args
classInstance
result
final
Sie können jede dieser Bausteinmethoden überschreiben. Schauen wir uns an, wie wir einen Zeitstempel hinzufügen können. Ich sage nicht, dass wir sollten. Pino , Winston und viele andere Logger können Zeitstempel selbst hinzufügen. Das Beispiel ist also rein lehrreich.
import { ClassLoggerFormatterService, IClassLoggerFormatterStartData, setConfig, } from 'class-logger' class ClassLoggerTimestampFormatterService extends ClassLoggerFormatterService { protected base(data: IClassLoggerFormatterStartData) { const baseSuper = super.base(data) const timestamp = Date.now() const baseWithTimestamp = `${timestamp}:${baseSuper}` return baseWithTimestamp } } setConfig({ formatter: new ClassLoggerTimestampFormatterService(), })
Live-Demo
Fazit
Vergessen Sie bitte nicht, die Installationsschritte zu befolgen und sich mit den Anforderungen vertraut zu machen , bevor Sie sich für die Verwendung dieser Bibliothek entscheiden.
Hoffentlich haben Sie etwas Nützliches für Ihr Projekt gefunden. Zögern Sie nicht, mir Ihr Feedback mitzuteilen! Ich freue mich über Kritik und Fragen.