
Es gibt viele Fragen zur Arbeit von Diensten in den Phasen Entwicklung, Test und Support, und alle sind auf den ersten Blick anders als:
"Was ist passiert?" "Gab es eine Anfrage?" ,
"Was ist das Datumsformat?" ,
"Warum reagiert der Dienst nicht?" usw.
Ein korrekt zusammengestelltes Protokoll kann diese und viele andere Fragen ohne Beteiligung der Entwickler absolut autonom im Detail beantworten. Um dieses verlockende Ziel zu erreichen, wurde die Eclair-Protokollbibliothek ins Leben gerufen, die darauf ausgelegt ist, mit allen Teilnehmern des Prozesses in einen Dialog zu treten, ohne zu viele Decken zu ziehen.
Über die Decke und die Funktionen der Lösung - unten.
Was ist das Problem der Protokollierung
Wenn Sie nicht sehr daran interessiert sind, die Räumlichkeiten zu verstehen, können Sie sofort mit der Beschreibung unserer Lösung fortfahren.
- Das Anwendungsprotokoll ist sein Alibi.
Meistens kann nur er den Erfolg der Bewerbung nachweisen. In einem Mikrodienst gibt es keinen Zustand, benachbarte Systeme sind mobil und pingelig. "Wiederholen", "neu erstellen", "überprüfen" - all dies ist schwierig und / oder unmöglich. Das Protokoll sollte informativ genug sein, um die Frage „Was ist passiert?“ Zu beantworten . . Das Protokoll sollte für alle klar sein: für den Entwickler, den Tester, manchmal für den Analysten, manchmal für den Administrator, manchmal für die erste Supportlinie - alles passiert. - Bei Microservices geht es um Multithreading.
An den Dienst kommende Anforderungen (oder vom Dienst angeforderte Daten) werden meistens von mehreren Threads verarbeitet. Das Protokoll aller Threads wird normalerweise gemischt. Möchten Sie zwischen parallelen Threads und zwischen "sequentiellen" Threads unterscheiden? Derselbe Stream wird für die sequentielle Verarbeitung von Anforderungen wiederverwendet, wobei immer wieder eine eigene Logik für verschiedene Datensätze ausgeführt wird. Diese "sequentiellen" Flüsse fließen aus einer anderen Ebene, aber ihre Grenzen sollten dem Leser klar sein. - Das Protokoll sollte das ursprüngliche Datenformat speichern.
Wenn in Wirklichkeit Dienste über XML ausgetauscht werden, sollte das entsprechende Protokoll XML speichern. Es ist nicht immer kompakt und nicht immer schön (aber praktisch). Es ist einfacher, den Erfolg zu erkennen und Fehler zu analysieren. In einigen Fällen kann das Protokoll verwendet werden, um die Anforderung manuell abzuspielen oder erneut zu verarbeiten. - Ein Teil der Daten im Protokoll erfordert eine spezielle Beziehung.
Eingehende Daten (Anforderungen), ausgehende Daten (Antworten), Anforderungen an Systeme von Drittanbietern und Antworten von diesen müssen häufig separat gespeichert werden. Sie unterliegen besonderen Anforderungen: Haltbarkeit oder Zuverlässigkeit. Darüber hinaus können diese Daten im Vergleich zu einer typischen Protokollzeile eine beeindruckende Menge aufweisen. - Ein Teil der Daten ist nicht für das Protokoll.
Folgendes sollte normalerweise aus dem regulären Protokoll ausgeschlossen werden: Binärdaten (Byte-Arrays, base64, ..), personenbezogene Daten von Kunden / Partnern / anderen Personen und juristischen Personen. Es ist immer eine individuelle Geschichte, aber systematisch und eignet sich nicht für die manuelle Steuerung.
Warum nicht Hände
Nehmen Sie
org.slf4j.Logger
(
org.slf4j.Logger
mit Appenders eines beliebigen Anzugs) und schreiben Sie alles, was erforderlich ist, in das Log. Eingänge zu den Hauptmethoden, Exits spiegeln bei Bedarf abgefangene Fehler, einige Daten wider. Ist das notwendig? Ja, aber:
- Die Menge an Code wächst unangemessen (ungewöhnlich). Dies ist zunächst nicht sehr auffällig, wenn Sie nur die grundlegendsten protokollieren (erfolgreiche Unterstützung übrigens mit diesem Ansatz).
- Das Anrufen des Loggers mit den Händen wird schnell zu Faulheit. Das Deklarieren eines
static
Feldes mit einem Logger ist zu faul (nun, Lombok kann dies für uns tun). Wir Entwickler sind faul. Und wir hören auf unsere Faulheit, das ist edle Faulheit: Sie verändert die Welt beharrlich zum Besseren. - Microservices sind nicht auf allen Seiten gut. Ja, sie sind klein und hübsch, aber es gibt eine Kehrseite: Es gibt viele! Eine einzelne Anwendung von Anfang bis Ende wird häufig von einem Entwickler geschrieben. Das Erbe taucht nicht vor seinen Augen auf. Glücklich, nicht mit auferlegten Regeln belastet, sieht der Entwickler es als Pflicht an, sein eigenes Protokollformat, sein Prinzip und seine eigenen Regeln zu erfinden. Dann setzt die Erfindung brillant um. Jede Klasse ist anders. Das ist ein Problem? Kolossal.
- Durch Refactoring wird Ihr Protokoll beschädigt. Selbst die allmächtige Idee wird ihn nicht retten. Das Aktualisieren des Protokolls ist ebenso unmöglich wie das Aktualisieren des Javadoc. Gleichzeitig wird zumindest Javadoc nur von Entwicklern gelesen (nein, niemand liest), aber die Zielgruppe der Protokolle ist viel breiter und das Entwicklungsteam ist nicht begrenzt.
- MDC (Mapped Diagnostic Context) ist ein integraler Bestandteil einer Multithread-Anwendung. Das manuelle Befüllen des MDC erfordert eine rechtzeitige Reinigung am Ende der Arbeit im Stream. Andernfalls besteht die Gefahr, dass Sie einen
ThreadLocal
an nicht verwandte Daten binden. Hände und Augen, um dies zu kontrollieren, ist unmöglich zu sagen.
Und so lösen wir diese Probleme in unseren Anwendungen.
Was ist Eclair und was kann es?
Eclair ist ein Tool, das das Schreiben von protokolliertem Code vereinfacht. Es ist hilfreich, die erforderlichen Metainformationen zum Quellcode zu sammeln, sie zur Laufzeit mit den in der Anwendung fliegenden Daten zu verknüpfen und an das übliche Protokollrepository zu senden, während ein Minimum an Code generiert wird.
Das Hauptziel ist es, das Protokoll für alle Teilnehmer am Entwicklungsprozess verständlich zu machen. Daher endet die Bequemlichkeit des Schreibens von Code und die Vorteile von Eclair nicht, sondern beginnen erst.
Eclair protokolliert kommentierte Methoden und Parameter:
- Protokolliert den Methodenein- / -ausgang aus der von der Methode zurückgegebenen Methode / Ausnahmen / Argumente / Werte
- filtert Ausnahmen, um sie spezifisch nach Typen zu protokollieren: nur bei Bedarf
- Variiert das "Detail" des Protokolls basierend auf den Anwendungseinstellungen für den aktuellen Speicherort: Im detailliertesten Fall werden beispielsweise die Werte der Argumente (alle oder einige) in der kürzesten Version gedruckt - nur die Tatsache, dass die Methode eingegeben wurde
- druckt Daten als JSON / XML / in einem anderen Format (sofort einsatzbereit für Jackson, JAXB): versteht, welches Format für einen bestimmten Parameter am besten geeignet ist
- versteht SpEL (Spring Expression Language) für die deklarative Installation und die automatische MDC-Reinigung
- schreibt in N Logger, der "Logger" im Verständnis von Eclair ist eine Bean in dem Kontext, der die
EclairLogger
Schnittstelle implementiert: Sie können den Logger angeben, der die Annotation nach Namen, Alias oder Standard verarbeiten soll - informiert den Programmierer über einige Fehler bei der Verwendung von Anmerkungen: Eclair weiß beispielsweise, dass es auf dynamischen Proxys (mit allen zugehörigen Funktionen) funktioniert, sodass es Ihnen mitteilen kann, dass die Anmerkung auf der
private
Methode niemals funktioniert - Akzeptiert Meta-Annotationen (wie Spring sie nennt): Sie können Ihre Annotationen für die Protokollierung mithilfe einiger grundlegender Annotationen definieren, um den Code zu reduzieren
- In der Lage, "sensible" Daten beim Drucken zu maskieren: sofort einsatzbereites XPath-Shielding-XML
- schreibt ein Protokoll im "manuellen" Modus, definiert den Aufrufer und "erweitert" die Argumente, die den
Supplier
implementieren: Geben Sie die Möglichkeit, die Argumente "träge" zu initialisieren.
So verbinden Sie Eclair
Der Quellcode wird auf GitHub unter der Apache 2.0-Lizenz veröffentlicht.
Zum Herstellen einer Verbindung benötigen Sie Java 8, Maven und Spring Boot 1.5+. Vom Maven Central Repository gehostetes Artefakt:
<dependency> <groupId>ru.tinkoff</groupId> <artifactId>eclair-spring-boot-starter</artifactId> <version>0.8.3</version> </dependency>
Der Starter enthält eine Standardimplementierung von
EclairLogger
, die ein von Spring Boot initialisiertes Protokollierungssystem mit einigen überprüften Einstellungen verwendet.
Beispiele
Hier einige Beispiele für die typische Bibliotheksnutzung. Zuerst wird ein Codefragment angegeben, dann das entsprechende Protokoll, abhängig von der Verfügbarkeit einer bestimmten Protokollierungsstufe. Eine vollständigere Reihe von Beispielen finden Sie im Projekt-Wiki im Abschnitt
Beispiele .
Einfachstes Beispiel
Die Standardstufe ist DEBUG.
@Log void simple() { }
Wenn Level verfügbar ist | ... dann wird das Protokoll so sein |
---|
TRACE DEBUG | DEBUG [] rteeExample.simple > DEBUG [] rteeExample.simple < |
INFO WARN ERROR | - |
Die Protokolldetails hängen von der verfügbaren Protokollierungsstufe ab.
Die am aktuellen Speicherort verfügbare Protokollierungsstufe wirkt sich auf die Protokolldetails aus. Je niedriger die verfügbare Ebene (dh je näher an TRACE), desto detaillierter ist das Protokoll.
@Log(INFO) boolean verbose(String s, Integer i, Double d) { return false; }
Level | Protokoll |
---|
TRACE DEBUG | INFO [] rteeExample.verbose > s="s", i=4, d=5.6 INFO [] rteeExample.verbose < false |
INFO | INFO [] rteeExample.verbose > INFO [] rteeExample.verbose < |
WARN ERROR | - |
Feinabstimmung der Ausnahmeprotokollierung
Arten von protokollierten Ausnahmen können gefiltert werden. Ausgewählte Ausnahmen und deren Nachkommen werden verpfändet. In diesem Beispiel wird
NullPointerException
auf der WARN-Ebene,
Exception
auf der ERROR-Ebene (standardmäßig) und
Error
nicht protokolliert (da
Error
nicht im Filter der ersten Annotation
@Log.error
und explizit vom Filter der zweiten Annotation ausgeschlossen wird).
@Log.error(level = WARN, ofType = {NullPointerException.class, IndexOutOfBoundsException.class}) @Log.error(exclude = Error.class) void filterErrors(Throwable throwable) throws Throwable { throw throwable; }
Level | Protokoll |
---|
TRACE DEBUG INFO WARN | WARN [] rteeExample.filterErrors ! java.lang.NullPointerException java.lang.NullPointerException: null at rteeExampleTest.filterErrors(ExampleTest.java:0) .. ERROR [] rteeExample.filterErrors ! java.lang.Exception java.lang.Exception: null at rteeExampleTest.filterErrors(ExampleTest.java:0) ..
|
ERROR | ERROR [] rteeExample.filterErrors ! java.lang.Exception java.lang.Exception: null at rteeExampleTest.filterErrors(ExampleTest.java:0) .. |
Stellen Sie jeden Parameter separat ein
@Log.in(INFO) void parameterLevels(@Log(INFO) Double d, @Log(DEBUG) String s, @Log(TRACE) Integer i) { }
Level | Protokoll |
---|
TRACE | INFO [] rteeExample.parameterLevels > d=9.4, s="v", i=7 |
DEBUG | INFO [] rteeExample.parameterLevels > d=9.4, s="v" |
INFO | INFO [] rteeExample.parameterLevels > 9.4 |
WARN ERROR | - |
Druckformat auswählen und anpassen
Die für das Druckformat verantwortlichen „Drucker“ können von Vor- und Nachbearbeitern konfiguriert werden. Im obigen Beispiel ist
maskJaxb2Printer
konfiguriert, dass Elemente, die dem XPath-Ausdruck
"//s"
entsprechen, mit
"********"
maskiert werden. Gleichzeitig druckt
Dto
"wie es ist".
@Log.out(printer = "maskJaxb2Printer") Dto printers(@Log(printer = "maskJaxb2Printer") Dto xml, @Log(printer = "jacksonPrinter") Dto json, Integer i) { return xml; }
Level | Protokoll |
---|
TRACE DEBUG | DEBUG [] rteeExample.printers > xml=<dto><i>5</i><s>********</s></dto>, json={"i":5,"s":"password"} DEBUG [] rteeExample.printers < <dto><i>5</i><s>********</s></dto> |
INFO WARN ERROR | - |
Mehrere Logger im Kontext
Die Methode wird mit mehreren Loggern gleichzeitig protokolliert: Standardmäßig Logger (mit
@Primary
kommentiert) und auditLogger
auditLogger
. Sie können mehrere Protokollierer definieren, wenn Sie protokollierte Ereignisse nicht nur nach Ebenen (TRACE - ERROR) trennen, sondern auch an verschiedene Speicher senden möchten. Beispielsweise kann der
auditLogger
mit slf4j ein Protokoll in eine Datei auf der Festplatte schreiben, und
auditLogger
kann ein spezielles Daten-Slice in seinem eigenen Format in einen ausgezeichneten Speicher (z. B. in Kafka) schreiben.
@Log @Log(logger = "auditLogger") void twoLoggers() { }
MDC-Verwaltung
Mit Annotation festgelegte MDCs werden nach dem Beenden der annotierten Methode automatisch gelöscht. Ein MDC-Datensatzwert kann mithilfe von SpEL dynamisch berechnet werden. Beispiele sind: eine statische Zeichenfolge, die von einer Konstanten wahrgenommen wird, die den Ausdruck
1 + 1
jacksonPrinter
, die
jacksonPrinter
aufruft und die
static
Methode
randomUUID
.
MDCs mit dem Attribut
global = true
werden nach dem Beenden der Methode nicht gelöscht: Wie Sie sehen, ist der einzige Datensatz, der bis zum Ende des Protokolls im MDC verbleibt, die
sum
.
@Log void outer() { self.mdc(); } @Mdc(key = "static", value = "string") @Mdc(key = "sum", value = "1 + 1", global = true) @Mdc(key = "beanReference", value = "@jacksonPrinter.print(new ru.tinkoff.eclair.example.Dto())") @Mdc(key = "staticMethod", value = "T(java.util.UUID).randomUUID()") @Log void mdc() { self.inner(); } @Log.in void inner() { }
Protokoll bei der Ausführung des obigen Codes:
DEBUG [] rteeExample.outer >
DEBUG [beanReference={"i":0,"s":null}, sum=2, static=string, staticMethod=01234567-89ab-cdef-ghij-klmnopqrstuv] rteeExample.mdc >
DEBUG [beanReference={"i":0,"s":null}, sum=2, static=string, staticMethod=01234567-89ab-cdef-ghij-klmnopqrstuv] rteeExample.inner >
DEBUG [beanReference={"i":0,"s":null}, sum=2, static=string, staticMethod=01234567-89ab-cdef-ghij-klmnopqrstuv] rteeExample.mdc <
DEBUG [sum=2] rteeExample.outer <
Parameterbasierte MDC-Installation
Wenn Sie den MDC mithilfe der Anmerkung zum Parameter angeben, ist der mit Anmerkungen versehene Parameter als Stammobjekt des Auswertungskontexts verfügbar. Hier ist
"s"
ein Feld der Klasse
Dto
Typ
String
.
@Log.in void mdcByArgument(@Mdc(key = "dto", value = "#this") @Mdc(key = "length", value = "s.length()") Dto dto) { }
Protokoll bei der Ausführung des obigen Codes:
DEBUG [length=8, dto=Dto{i=12, s='password'}] rteeExample.mdcByArgument > dto=Dto{i=12, s='password'}
Manuelle Protokollierung
Für die "manuelle" Protokollierung reicht es aus, die Implementierung von
ManualLogger
zu implementieren. Übergebene Argumente, die den Schnittstellenanbieter implementieren, werden nur bei Bedarf "erweitert".
@Autowired private ManualLogger logger; @Log void manual() { logger.info("Eager logging: {}", Math.PI); logger.debug("Lazy logging: {}", (Supplier) () -> Math.PI); }
Level | Protokoll |
---|
TRACE DEBUG | DEBUG [] rteeExample.manual > INFO [] rteeExample.manual - Eager logging: 3.141592653589793 DEBUG [] rteeExample.manual - Lazy logging: 3.141592653589793 DEBUG [] rteeExample.manual < |
INFO | INFO [] rteeExample.manual - Eager logging: 3.141592653589793 |
WARN ERROR | - |
Was macht Eclair nicht?
Eclair weiß nicht, wo Sie Ihre Protokolle wie lange und im Detail speichern werden. Eclair weiß nicht, wie Sie Ihr Protokoll verwenden möchten. Eclair extrahiert sorgfältig alle benötigten Informationen aus Ihrer Anwendung und leitet sie an den von Ihnen konfigurierten Speicher weiter.
Eine Beispielkonfiguration von
EclairLogger
, die ein Protokoll an einen Logback-Logger mit einem bestimmten Appender weiterleitet:
@Bean public EclairLogger eclairLogger() { LoggerFacadeFactory factory = loggerName -> { ch.qos.logback.classic.LoggerContext context = (LoggerContext) LoggerFactory.getILoggerFactory(); ch.qos.logback.classic.Logger logger = context.getLogger(loggerName);
Diese Lösung ist nicht jedermanns Sache.
Bevor Sie Eclair als Hauptwerkzeug für die Protokollierung verwenden, sollten Sie sich mit einer Reihe von Funktionen dieser Lösung vertraut machen. Diese „Funktionen“ sind darauf zurückzuführen, dass Eclair auf dem Standard-Proxy-Mechanismus für Spring basiert.
- Die Ausführungsgeschwindigkeit des im nächsten Proxy eingeschlossenen Codes ist unbedeutend, sinkt jedoch. Für uns sind diese Verluste selten signifikant. Wenn sich die Frage nach einer Verkürzung der Vorlaufzeit stellt, gibt es viele effektive Optimierungsmaßnahmen. Die Ablehnung eines praktischen informativen Protokolls kann als eine der Maßnahmen angesehen werden, jedoch nicht in erster Linie.
- StackTrace "aufblähen" etwas mehr. Wenn Sie nicht an die langen StackTrace of Spring-Proxys gewöhnt sind, kann dies ein Ärgernis für Sie sein. Aus einem ebenso offensichtlichen Grund wird das Debuggen von Proxy-Klassen schwierig sein.
-
Nicht jede Klasse und nicht jede Methode kann als Proxy verwendet werden :
private
Methoden können nicht als Proxy verwendet werden. Um eine Methodenkette in einer Bean zu protokollieren, die Sie selbst benötigen, können Sie nichts als Proxy verwenden, das keine Bean ist usw.
Am Ende
Es ist völlig klar, dass dieses Tool wie jedes andere verwendet werden kann, um davon zu profitieren. Und dieses Material beleuchtet nur oberflächlich die Seite, auf der wir uns auf die Suche nach der perfekten Lösung gemacht haben.
Kritik, Gedanken, Hinweise, Links - Ich begrüße Ihre Teilnahme am Leben des Projekts sehr! Ich würde mich freuen, wenn Sie Eclair für Ihre Projekte nützlich finden.