Im Rahmen eines unserer Projekte wurden wir mit vielen Debugging-Fällen konfrontiert. Einfache Meldungen in den Protokollen sind nicht immer ausreichend. Zum Beispiel kann es notwendig sein, mehr Informationen über den Benutzer zu haben, der die Aktion ausgeführt hat (seine IP, seine Berechtigungen, seine Kennung, …). In unserem Fall bestand unsere Anwendung aus mehreren Microservices, und wir wollten den Fluss, dem eine Anfrage folgt, die von einem Microservice zum anderen geht, genau identifizieren. Zu diesem Zweck wurde ein eindeutiger Bezeichner erzeugt und in den Protokollen jeder Webanwendung angezeigt. Dies hat uns sehr geholfen, Probleme mit Anwendungen von Drittanbietern zu lösen, die wir verwendet haben.
- Wie werden Nachrichten protokolliert?
- Welche Kontextinformationen sollen unseren Nachrichten hinzugefügt werden?
- Kann diese Information hinzugefügt werden, wenn asynchrone Threads ausgeführt werden?
Dieser Artikel wird Ihnen helfen, eine Spring Boot Java-Anwendung zu erstellen, um Nachrichten mit Log4j zu protokollieren und den MDC dieser Bibliothek (Mapping Diagnostic Context) zu verwenden, um Kontextdaten zusätzlich zu den Nachrichten hinzuzufügen, insbesondere für asynchrone Aufgaben.
Beginnen wir mit der Erstellung einer klassischen Spring Boot-Anwendung mit der integrierten Log4j-Bibliothek. Diese Bibliothek ermöglicht es uns, einen Logger zu verwenden, der Protokollmeldungen verschiedener Typen (Info, Fehler, Warnung, …)
- Erstellen Sie auf Spring Initializr (https://start.spring.io/) ein einfaches Spring Boot-Projekt ohne Abhängigkeiten.
- Bearbeiten Sie die Datei pom.xml, um die Abhängigkeiten hinzuzufügen, die für die Verwendung der Log4j-Bibliothek erforderlich sind
- Erstellen Sie die Datei src/main/resources/log4j2.xml-Datei, die das Format für zukünftige Log-Meldungen definiert
pom.xml
src/main/resources/log4j2.xml
Anzeigen unserer ersten Log-Meldung
Im aktuellen Zustand, wenn wir die Anwendung starten (z.B. über eine IDE oder mit Maven), erscheint keine Log-Meldung. Wir werden eine Komponente erstellen, die die Log4j-Bibliothek verwendet, um eine Informationsmeldung zu protokollieren.
Erstellen Sie eine Datei src/main/java/com/sipios/example/mdc/Execute.java mit dem folgenden Code. Der Paketname ist hier com.sipios.example.mdc, er sollte natürlich durch Ihren ersetzt werden 🙂
src/main/java/com/sipios/example/mdc/Execute.java
Wenn Sie die Anwendung ausführen, wird nun eine erste Protokollmeldung angezeigt, die dem definierten Format entspricht. Es ist auch möglich, die Methoden error
und warning
zu verwenden. Logmeldungen, die diesen Typen entsprechen, werden angezeigt.
Verwenden Sie MDC (Mapping Diagnostic Context) in Ihrem Log
Nachdem wir nun wissen, wie man die Log4j-Bibliothek verwendet, können wir den Mapping Diagnostic Context (MDC) verwenden, der es uns ermöglicht, eine Datenkarte mit unserer Meldung zu verbinden. Einige Beispiele für Daten, die wir Ihnen empfehlen, in den MDC aufzunehmen:
- Daten der aktuellen Sitzung (Benutzer, Abfrage, Anfrage …)
- Metriken über die Ausführung des Prozesses (Anfangszeit und Ausführungszeit, …)
- Informationen über die Version der Anwendung
- …
Diese Map wird in den Logs angezeigt, wenn die Maske %X
in der Log4j Message Format Definition verwendet wird. Dies ist hier in unserer Datei src/main/resources/log4j2.xml der Fall. In der vorherigen Ausführung sehen wir {}
, was auf eine leere Map hinweist.
Die Verwendung von MDC ist sehr einfach und wird wie eine klassische Map über die Methoden put
, get
, remove
, clear
verwendet… Fügen wir der MDC zwei Einträge hinzu und führen die Anwendung aus.
src/main/java/com/sipios/example/mdc/Execute.java
MDC ist global und bleibt erhalten, solange sie nicht verändert wird. Wenn Sie ihn leeren wollen, indem Sie zum Beispiel eine Komponente verlassen, verwenden Sie einfach die Methode clear
. Dies würde dann das folgende Ergebnis liefern.
Wie funktioniert es mit asynchronen Komponenten?
Lassen Sie uns MDC mit asynchronen Komponenten ausprobieren (eine Komponente, die auf einem parallelen Thread zum Hauptthread ausgeführt wird)! Zunächst einmal müssen wir unsere Anwendung so konfigurieren, dass sie solche Beans ausführen kann. Wir erstellen einen Dienst mit zwei Methoden, eine ist synchron und die andere asynchron.
- Anmerkung @EnableAsync zur Klasse Application hinzufügen
- Einen Dienst mit einer normalen und einer @Async-Methode erstellen
- Die Komponente so modifizieren, dass die Methoden des Dienstes injiziert und verwendet werden
- Die Anwendung starten
src/main/java/com/sipios/example/mdc/Application.java
src/main/java/com/sipios/example/mdc/service/Example.java
src/main/java/com/sipios/example/mdc/Execute.java
Add taskExecutor in Application class
src/main/java/com/sipios/example/mdc/Application.java
Re-execute :
Wie wir sehen können, ist MDC in async thread leer. In der Tat wird jede asynchrone Aufgabe in einem neuen Thread gestartet. Jeder Thread ist mit einem Map MDC verbunden, das vom Task Executor initiiert wird. Es ist möglich, auf diesem Executor zu spielen, um das gleiche MDC wie auf dem Hauptthread zu erhalten. Fügen wir einen Dekorator in asyncExecutor hinzu, um MDC zu duplizieren!
src/main/java/com/sipios/example/mdc/ExampleTaskDecorator.java
Setzen Sie diesen Dekorator in async config
src/main/java/com/sipios/example/mdc/Application.java
Relaunch application
So geht’s! Wir können in den Protokollen den gesamten Kontext übermitteln, den wir in synchronen oder asynchronen Tasks wünschen.
Damit wird das Debuggen einer Anwendung vereinfacht und verständlicher. Im Rahmen unseres Projekts hat uns dies viel Zeit im Austausch mit unseren Mitwirkenden gespart. Jetzt liegt es an Ihnen 🙂
Bibliographie
- http://www.baeldung.com/mdc-in-log4j-2-logback
- https://spring.io/guides/gs/async-method/
- https://moelholm.com/2017/07/24/spring-4-3-using-a-taskdecorator-to-copy-mdc-data-to-async-threads/