Loggingdaten-Einlaufstelle mit Komfortfunktionen
Best of NuGet, Teil 5

Der Gedanke, Loggingdaten über einen zentralen Dienst zu sammeln und zur Weiterverarbeitung zur Verfügung zu stellen, war außerhalb von Echtzeitbetriebssystemen nur im akademischen Bereich verbreitet. Nach Android änderte sich dies, da das zentralisierte Verwalten erstens Aufwand einspart und zweitens verschiedene fortgeschrittene Logging-Methoden ermöglicht.
Serilog erlaubt Entwicklern erstens das Speichern „formalisierter“ Daten, in denen später gesucht werden kann. Zweitens ermöglicht die offene Architektur das Einbinden verschiedenster Erweiterungsmodule, die die Logging-Daten persistieren oder auf andere Weise weiterreichen können.
Von der Architektur des Logging-Systems
Serilog wurde von Anfang an als „offenes System“ aufgebaut: Alle eingezeichneten Elemente im vom Autor erstellten Flussdiagramm in Bild 1 lassen sich entweder erweitern oder zur Gänze neu implementieren.

Der Entwicklercode selbst interagiert mit dem Logger. Dabei handelt es sich um eine Klasse, die Logging-Nachrichten entgegennimmt und diese – unter anderem – durch zusätzliche Metainformationen erweitert, die von als Enricher bezeichneten Kompositoren angeliefert werden.
Im nächsten Schritt wandern diese vom Logger zusammengestellten Informationen in einen Filter und/oder in Unterlogger. Am Ende der Signalkette stehen dann als Sink bezeichnete Elemente, die sich um die Persistierung beziehungsweise Sichtbarmachung der angefallenen Protokollinformationen bemühen.
Hervorzuheben ist die Rolle der Filter-Instanzen. Die idiomatische Nutzung von Serilog setzt das Anliefern der Logging-Informationen in Form von Parametern voraus – von Haus aus werden dabei die in Bild 2 aufgelisteten Typen unterstützt.

Bei idiomatisch korrekter Nutzung können die Filter dann mit diesen Informationen makeln. Durch Nutzung eines Unterloggers wäre es beispielsweise möglich, nur jene Informationen in einen Cloudspeicher zu schreiben, die einen gewissen „Wichtigkeitsgrad“ aufweisen.
Inbetriebnahme des Frameworks
Serilog funktioniert im Prinzip mit jeder Darbietungsform einer .NET-Applikation. Übungshalber wollen wir in den folgenden Schritten auf die Vorlage Konsolen-App setzen und den Projektnamen NMGSerilog vergeben. Als Framework-Version reicht die von Visual Studio 2022 als Default ausgegebene 8.0 aus.
Die Magie von NuGet erleichtert dann die Einbindung der benötigten Pakete. Da das Basispaket von Serilog nur die Interfaces, aber keine praktischen Implementierungen mitbringt, benötigen wir fürs Erste mindestens einen Konsolen-Sink. Das Markieren des Pakets Serilog.Sinks.Console als zu Deployen sorgt dafür, dass NuGet automatisch auch das Basispaket Serilog in die Solution einbindet.
Im nächsten Schritt wollen wir Serilog zunächst wie ein Analogon zu LogCat und Co. verwenden. Hierzu ist folgender Code erforderlich:
Log.Logger = new LoggerConfiguration() .WriteTo.Console() .CreateLogger(); Log.Information("Die Wefze Anna faucht mit 40 Dezibel"); Log.Warning("Lärm stört die innere Ruhe des Menschen und macht krank!");
Die erste Aktion ist dabei – man denke an das Flussdiagramm weiter oben – die Erzeugung eines Logger-Objekts. Die Methode WriteTo.Console() ist dabei dafür verantwortlich, die Konsolen-Ausgabe-Senke als Teil der Pipeline festzulegen. Im nächsten Schritt nutzen wir dann sowohl die Methoden Information als auch Warning, um gesundheitsfördernde Informationen in das Logging-System zu schreiben. Bild 3 zeigt das Ergebnis.

Die Logdaten erscheinen in der Kommandozeile (Bild 3)
AutorInteressant ist hier unter anderem der in eckigen Klammern gesetzte Teil des Strings. Dabei handelt es sich um Informationen im Hinblick auf die Wichtigkeit – der mit Information abgesetzte Informations-Aufruf und die Warnung werden mit unterschiedlichen Präfixen dargestellt, um direkt eine visuelle Unterscheidung zu erleichtern.
Aus Serilog-idiomatischer Sicht ist der String ungünstig, weil er zwei filtrierbare Informationen enthält: einerseits den Namen der Quelle der Musik und andererseits die Intensität der erzeugten Lautstärke.
Durch Separierung dieser Felder könnten wir, wie weiter oben erwähnt, gezieltes Filtrieren ermöglichen. Hierzu ist es notwendig, den Aufruf der Information-Methode nach folgendem Schema anzupassen und die Templates als separate Variablen anzuliefern:
Log.Information("Die Wefze {Name} faucht mit {Lautheit} Dezibel", "Annette", 90);
Ob der Information-Aufruf korrekt parametriert ist, lässt sich dadurch verifizieren, dass die Parameter wie in der Bild 4 gezeigt farblich hervorgehoben angezeigt werden.

Parameter erscheinen farblich hervorgehoben (Bild 4)
AutorKulturelle Bereicherung für Logs
Beim Erzeugen von Protokollen gibt es Datenarten, die man immer wieder zu protokollieren sucht – ein Beispiel könnte der Name der Maschine sein, die für die Aufzeichnung des Ärgernisses verantwortlich war. Zur Lösung dieses Problems gibt es, wie weiter oben im Diagramm gezeigt, als Enricher bezeichnete Komponenten, die – nomen est omen – ihren Beitrag zum durch den Logger marschierenden Datenstrom leisten.
Aufgrund der weiten Verbreitung von Serilog bietet es sich an dieser Stelle an, in NuGet nach dem String Serilog.Enrich zu suchen. Bild 5 zeigt einen Auszug der zur Verfügung stehenden Fähigkeiten.

Serilog stellt Dutzende von Enrichern zur Verfügung (Bild 5)
AutorIn den folgenden Schritten wollen wir übungshalber den Maschinenamen persistieren, weshalb das Paket Serilog.Enrichers.Environment zur Solution hinzuzufügen ist. Angemerkt sei, dass dieses Paket nicht nur Maschinenname und Co. persistieren kann – es ist auch zum Aufzeichnen der in Umgebungsvariablen befindlichen Werte befähigt.
Für einen ersten, stupiden Versuch bietet sich dann nach folgendem Schema aufgebauter Code an:
Log.Logger = new LoggerConfiguration() .WriteTo.Console() .Enrich.WithMachineName() .CreateLogger();
Seine Ausführung führt allerdings nur zum weiter oben gezeigten Ergebnis. In der Serilog-Basisimplementierung steht mit der Methode WithProperty ein Basis-Enricher zur Verfügung, der „Konstanten“ in den Logger-Strom schreiben sollte. Auch hier bietet sich ein nach folgendem Schema aufgebauter naiver Versuch an, der allerdings ebenfalls fehlschlägt:
Log.Logger = new LoggerConfiguration() .Enrich.WithMachineName() .Enrich.WithEnvironmentUserName() .Enrich.WithProperty("Version", "1.0.0") .WriteTo.Console() .CreateLogger();
Der springende Punkt ist, dass die verschiedenen Logger im Allgemeinen ein Template erwarten, dass das Format die in der jeweiligen Sink abzulegenden Messages festlegt. Die Ausgabe des Maschinennamens funktioniert beispielsweise erst dann, wenn wir im Feld outputTemplate nach folgendem Schema das gewünschte Format festlegen:
.WriteTo.Console(outputTemplate: "[{Level}] ({MachineName}) {Message}{NewLine}") .CreateLogger();
Ergebnis der Programmausführung ist dann das in Bild 6 gezeigte Verhalten.

Filtern der Ereignisse
Das weiter oben erfolgende Hervorheben von Namen und Lautheit ist kein Selbstzweck. Als letztes „Husarenstück“ wollen wir unseren Logger an dieser Stelle um die Fähigkeit erweitern, nur besonders lästige Ausgaben zu persistieren.
Hierzu ist neben dem Hinzufügen weiterer Lästlinge die Nutzung eines Filters erforderlich:
Log.Logger = new LoggerConfiguration() .Enrich.WithMachineName() .Enrich.WithEnvironmentUserName() .Enrich.WithProperty("Version", "1.0.0") .Filter.ByExcluding(Matching.WithProperty<int>("Lautheit", p => p < 50)) .WriteTo.Console(outputTemplate: "[{Level}] ({MachineName}) {Message}{NewLine}") .CreateLogger(); Log.Information("Die Wefze {Name} faucht mit {Lautheit} Dezibel", "Annette", 90); Log.Information("Die Wefze {Name} faucht mit {Lautheit} Dezibel", "Batta", 20); Log.Information("Die Wefze {Name} faucht mit {Lautheit} Dezibel", "Claire", 25); Log.Warning("Lärm stört die innere Ruhe des Menschen und macht krank!");
Daraus resultiert, dass SeriLog fortan nur eine der drei Quellen von Lärm persistiert. Zu beachten ist, dass ByExcluding hier nur die Lautheit ausschließt – Meldungen ohne Lautheit kommen also prinzipiell in die Ausgabe.

Nur die Lästigsten kommen in das Log ... (Bild 7)
AutorFazit
Wer die eigenen Logging-Informationen über das Serilog-Framework zur Aberntung bereitstellt, erspart sich in der Praxis Unmengen von Code. Die aus Platzgründen nicht besprochenen diversen Cloud-Logger ermöglichen beispielsweise das direkte Hochladen der Logging-Ergebnisse in alle Arten von Clouddienst – ein immens wertvolles Feature, dessen Nützlichkeit auf keinen Fall unterschätzt werden sollte.