Logging

Die jadice Codebasis macht an vielen Stellen Gebrauch von Logging um auf Probleme hinzuweisen und Nachvollziehbarkeit zu ermöglichen. Zur Nutzung von Logging existieren mehrere allgemein erhältliche Frameworks, die verschiedene Ansätze verfolgen und über unterschiedlichen Leistungsumfang verfügen.

Da die jadice document platform zur Einbindung in eine Gesamtapplikation konzipiert ist, legt sie sich nicht auf die Verwendung eines bestimmten Logging-Frameworks fest. Es existiert stattdessen die jadice Logging Framework Façade, die das Logging-System der integrierenden Applikation mitverwenden kann.

Die jadice Logging Framework Façade

Das jadice Logging-Framework erlaubt ein direktes und einfaches Einbinden der bekanntesten und verbreitetsten Logging-Systeme und -Frameworks, wie JDK Logging, Log4J, Log4J2, SLF4J und via SLF4J Logback, JCL, x4Juli und viele andere mehr. Sofern Sie bereits eines der erwähnten Frameworks einsetzen, ist zur Ausgabe von jadice-Meldungen in eines dieser Logging-Systeme oft nur eine Modifikation des Klassenpfads notwendig.

Falls kein bestimmtes Logging-Delegate, sprich ein bestimmtes Logging-Framework, durch den Klassenpfad vorgegeben oder verfügbar ist, wird der in Java enthaltene JDK Logger als Fallback verwendet. Da der JDK-Logger immer zur Verfügung steht, ist es unerheblich, wann das gewünschte Ziel Logging System im Klassenpfad zur Verfügung gestellt wird. Auch ist es möglich während der Entwicklungsphase ein anderes Logging System zu verwenden als im Produktivsystem. Durch Verwendung der Logging Façade ist selbst ein späterer Wechsel des Logging Systems ohne weiteren Programmieraufwand möglich.

Die folgenden Abschnitte gehen zunächst auf die Verwendung der Logging Façade mit unterschiedlichen Ziel-Logging-Frameworks ein. Nach Hinweisen zu möglichen Fehlerquellen und sinnvoller Konfiguration wird noch auf einige Details zu Log-Meldungen aus der jadice document platform eingegangen.

Verwendung existierender Logging-Systeme

Die Auslieferung der jadice document platform stellt neben dem Standard JDK Logger drei weitere Implementationen von Logging-Delegates zur Verfügung. Soll Log4J oder Log4J2 verwendet werden, fügen Sie die entsprechende Log4J(2)-Implementation dem Klassenpfad der Anwendung hinzu. Zur Verwendung eines anderen Logging-Frameworks nehmen Sie stattdessen die entsprechende SLF4J-Implementation in den Klassenpfad auf.

Eine detaillierte Beschreibung, wo die gewünschte Implementation des Logging-Delegates in der Auslieferung zu finden ist, ob und welche weiteren Schritte notwendig sind, entnehmen Sie bitte den folgenden Abschnitten.  

Die in der Auslieferung enthaltenen Logging-Delegates benötigen keine weitere spezifische Konfiguration. Eine Anpassung des Verhaltens des Ziel-Logging-Systems, zum Beispiel des Log-Levels oder ähnlichem, wird grundsätzlich über die Konfigurationsmöglichkeiten des jeweiligen Ziel-Logging-Systems gesteuert.

Log4J und Log4J2

Die Implementation des Logging-Delegates für Log4J(2) entspricht folgender Namenskonvention:

logging-log4j-version.jar beziehungsweise logging-log4j2-version.jar

In der Auslieferung finden Sie die entsprechenden Log4J(2)-Delegates, jeweils passend zu den im Einsatz befindlichen jadice Modulen, unter folgender Verzeichnisstruktur:

lib/logging

Fügen Sie bitte den zu Ihrer jadice Version passenden Log4J(2)-Delegate sowie auch Log4J(2) selbst in den Klassenpfad der Anwendung ein. Ist eine log4J-Konfiguration bereits auf dem Klassenpfad verfügbar, wird keine weitere Konfiguration benötigt.

Log4J(2) wie auch detaillierte Informationen zur Konfiguration finden Sie auf der Log4J Homepage. Die zugehörigen Manuals finden Sie unter Log4J Manual und Log4J2 Manual.

SLF4J

Die Namenskonvention des Logging-Delegates von SLF4J entspricht folgendem Schema:

logging-slf4j-version.jar

Wie auch für Log4J finden Sie das entsprechende SLF4J-Delegate, jeweils passend zu den im Einsatz befindlichen jadice Modulen, unter folgender Verzeichnisstruktur:

lib/logging

Fügen Sie das zu Ihrer jadice Version passende SLF4J Delegate in den Klassenpfad der Anwendung ein. Zusätzlich wird das slf4j-api-version.jar und eine Logging-Implementierung benötigt. Detaillierte Informationen über SLF4J und unterstützte Typen der Logging Delegates und Implementierungen finden Sie auf der SLF4J Homepage beziehungsweise im SLF4J Manual.

Mögliche Fehlermeldungen

Ein Großteil aller Klassen der jadice document platform generiert Logging-Ausgaben. Wird eine dieser Klassen aktiviert – also durch den ClassLoader geladen – fordert sie beim jadice Logging-Framework einen Logger an. Mit der ersten Anfrage an das Framework wird dieses initialisiert und durchsucht den Klassenpfad nach dem gewünschten Ziel-Logging-System. Sollte diese Suche fehlschlagen, wird die folgende Nachricht auf der Kommandozeile ausgegeben:

Unable to initialize logging.

Eine weitere Nachricht auf der Kommandozeile stellt daraufhin nähere Angaben über den Grund des Fehlschlags zur Verfügung. Es existieren zwei Arten von Gründen:

  1. No logging delegation implementation on classpath

    Tritt diese Fehlermeldung auf, so wurde keine der beiden in den vorigen Abschnitten beschriebenen Logging-Delegates für Log4J oder SLF4J im Klassenpfad gefunden. Es muss daher die .jar-Datei für das gewünschte Ziel-Logging-System korrekt dem Java-Klassenpfad hinzugefügt werden wie in den vorhergehenden Abschnitten beschrieben.

  2. Initialization failure due to exception

    Diese Fehlermeldung deutet auf ein Problem des zugrundeliegenden Ziel-Logging-Systems hin und kann daher vielfältige Ursachen haben. Im Allgemeinen sollte die Dokumentation des entsprechenden Systems konsultiert werden.

    Die folgenden Beispiele geben Anhaltspunkte für einzelne Fehlersituationen.

    Beispiel 4.1. Fehlende Log4J-Bibliothek

    Unable to initialize logging.
    Initialization failure due to exception:
    java.lang.NoClassDefFoundError: org/apache/log4j/LogManager
            at com.levigo.util.log.impl.Log4JLogFactory.<clinit>(Log4JLogFactory.java:23)
            at com.levigo.util.log.impl.LoggingBinder.getLoggerFactory(LoggingBinder.java:17)
            at com.levigo.util.log.LoggerFactory.<clinit>(LoggerFactory.java:24)
            ... (some more omitted entries)
    No logging binder available. Using JDK logging instead.

    In diesem Fall ist es wahrscheinlich, dass die Log4J Bibliothek nicht im Klassenpfad vorhanden ist. Zur Lösung des Problems muss die entsprechende .jar-Datei in den Klassenpfad der Applikation aufgenommen werden und sichergestellt werden, dass auf die Datei zugegriffen werden kann.


    Beispiel 4.2. Fehlende SLF4J-Bibliothek

    Die folgende Fehlerausgabe zeigt das gleiche Problem, dieses Mal jedoch durch das Fehlen der notwendigen SLF4J-Bibliothek hervorgerufen. Zur Lösung des Problems muss die entsprechende .jar-Datei in den Klassenpfad der Applikation aufgenommen werden und sichergestellt werden, dass auf die Datei zugegriffen werden kann.

    Unable to initialize logging.
    Initialization failure due to exception:
    java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory
            at com.levigo.util.log.impl.Slf4JLogFactory.<clinit>(Slf4JLogFactory.java:17)
            at com.levigo.util.log.impl.LoggingBinder.getLoggerFactory(LoggingBinder.java:17)
            at com.levigo.util.log.LoggerFactory.<clinit>(LoggerFactory.java:24)
            ... (some more omitted entries)
    No logging binder available. Using JDK logging instead.


    Beispiel 4.3. Fehlender Delegate für SLF4J

    Da es sich bei SLF4J selbst ebenfalls um eine Logging-Façade handelt, kann der folgende Fehler auftreten.

    SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
    SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further details.
    Unable to initialize logging.
    Initialization failure due to exception:
    java.lang.NoClassDefFoundError: org/slf4j/impl/StaticLoggerBinder
            at org.slf4j.LoggerFactory.<clinit>(LoggerFactory.java:60)
            at com.levigo.util.log.impl.Slf4JLogFactory.<clinit>(Slf4JLogFactory.java:17)
            at com.levigo.util.log.impl.LoggingBinder.getLoggerFactory(LoggingBinder.java:17)
            at com.levigo.util.log.LoggerFactory.<clinit>(LoggerFactory.java:24)
            at com.levigo.jadice.gui.AbstractJadicePanel.<clinit>(AbstractJadicePanel.java:42)
            ... (some more omitted entries)
    No logging binder available. Using JDK logging instead.

    Nähere Hinweise zur Behebung der Fehlersituation finden sich in der Dokumentation zu SLF4J.


Hinweise zur Optimierung der Logging-Konfiguration

Die einzelnen Logging-Systeme, mit denen jadice über die vorgestellte Façade verbunden werden kann, bieten jeweils spezielle Konfigurationsmöglichkeiten welche Meldungen im Log auftauchen oder ignoriert werden sollen.

Um geeignete Konfigurationen für das Logging aus der jadice document platform vornehmen zu können, sollen an dieser Stelle einige Hinweise gegeben werden. Da es sich bei Log4J um ein häufig eingesetztes Framework handelt, wird es für die im Folgenden aufgeführten Beispiele verwendet. Die erklärten Prinzipien lassen sich jedoch auf andere Frameworks übertragen.

Festlegen des Detailgrads der Log-Meldungen

Häufig – vor allem während der Implementationsphase – ist es gewünscht, für eine Gesamtapplikation nur die wichtigsten Log-Meldungen auszugeben, für einzelne Komponenten jedoch detaillierte Informationen zu erhalten. Die gebräuchlichen Logging-Frameworks unterstützen dieses Konzept indem eine Hierarchie von Loggern gebildet wird. Die Detailtiefe der erzeugten Log-Meldungen kann für Teile dieser Hierarchie festgelegt werden.

Die in jadice verwendeten Logger bilden ihre Hierarchie nach einer festen Konvention, die sich an den Klassennamen in denen der Logger verwendet wird orientiert. Bis auf wenige Ausnahmen trägt jeder Logger den voll qualifizierten Namen der Klasse, in der er benutzt wird. Dies ermöglicht eine sehr leichte Einschränkung der Logger-Konfiguration auf einzelne Klassen oder, über die Verwendung des Package-Namens, auf Teilkomponenten der jadice document platform.

Beispiel 4.4. Konfiguration des LogLevels für sämtliche jadice-Klassen unter Log4J

Beispielhaft soll an dieser Stelle gezeigt werden, wie der Detailgrad sämtlicher Logmeldungen der jadice document platform festgelegt werden kann ohne den Detailgrad für die integrierende Applikation zu beeinflussen. Das Beispiel verwendet Log4J mit einer XML-basierten Konfiguration. Da die Package-Namen sämtlicher jadice-Klassen mit dem konstanten Wert com.levigo. beginnen, wird dieser zur Konfiguration herangezogen.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE log4j:configuration PUBLIC "-//Apache//DTD Log4j 1.2//EN"
    "http://logging.apache.org/log4j/docs/api/org/apache/log4j/xml/log4j.dtd">

<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">

  <!-- ... general configuration ... -->

  <!-- for all jadice classes, log only error messages -->
  <logger name="com.levigo" additivity="true">
        <level value="error"/>
  </logger>

  <!-- for all other Loggers, log info messages -->
  <root>
    <level value="info" />
    <!-- ... specify logging destination via log4j appender ... -->
  </root>

</log4j:configuration>

Anmerkung: Durch den Parameter additivity="true" wird festgelegt, dass der deklarierte Logger die Grundeinstellungen des in der Hierarchie über ihm liegenden Loggers erben soll – in diesem Fall ist dies der Root-Logger. Bei den geerbten Einstellungen handelt es sich im Besonderen um den Logging-Appender, der beispielsweise angeben könnte, dass sämtliche Nachrichten auf die Konsole geschrieben werden sollen. Der Log-Level könnte ebenfalls mitvererbt werden, wird hier jedoch überschrieben und auf error geändert.


Beeinträchtigung der Performance durch Logging

Grundsätzlich sollte man sich darüber im Klaren sein, dass eine hohe Anzahl von Logausgaben die Ausführungsgeschwindigkeit der Applikation negativ beeinträchtigen kann. Würde beispielsweise in der Log4J-Konfiguration das Logging für sämtliche Klassen der jadice document platform auf debug gesetzt, hätte dies eine erhebliche Geschwindigkeitsbeeinträchtigung sowie gegebenenfalls die Erstellung großer Log-Dateien zur Folge. Für den Produktionseinsatz sollten Konfigurationen dieser Art daher vermieden werden. Auch zur Entwicklungszeit empfiehlt es sich, die Ausgabe detaillierter Log-Meldungen auf die jeweils relevanten Komponenten zu beschränken.

Die Formatierung der einzelnen Log-Ausgaben hat ebenfalls einen Einfluss auf die zur Generierung notwendige Rechenzeit. Die meisten Ziel-Logging-Systeme bieten Möglichkeiten, das Aussehen von Log-Messages zu bestimmen. Dabei wird auch festgelegt, welche Zusatzinformationen – wie beispielsweise der Klassenname oder ein Zeitstempel – enthalten sein sollen. Unter Log4J können Einstellungen dieser Art über ein sogenanntes PatternLayout vorgenommen werden. Insbesondere komplexere Formatierungen sollten nur eingestellt werden, wo es wirklich notwendig ist.

Einsatz von Logging in der jadice document platform

Nachdem in den vorhergehenden Abschnitten darauf eingegangen wurde, wie das Logging der jadice document platform in die einbettende Applikation integriert werden kann, sollen nun noch einige Details dazu erläutert werden, in welchen Fällen Log-Messages produziert werden und wie in anderen Programmteilen darauf reagiert werden kann.

Verwendung der Log Levels

Wie es auch in den Ziel-Logging-Systemen allgemein üblich ist, verwendet das jadice Logging Framework verschiedene Kategorien um die Wichtigkeit und Tragweite einer Log-Message einzuordnen. Die Kategorien werden Log Levels genannt und durch die Façade auf geeignete Levels des Ziel-Logging-Systems abgebildet. Es existieren mehrere Log Levels, die den von Log4J angebotenen entsprechen.

Die folgende Aufstellung zeigt, in welchen Fällen die einzelnen Levels innerhalb der jadice document platform verwendet werden.

FATAL

Massive Beeinträchtigung der Verarbeitung

ERROR

Beeinträchtigung von mindestens einem Teil der Verarbeitung

WARN

Möglicherweise ist ein Bearbeitungsschritt beeinträchtigt

INFO

Informationen zum Verarbeitungsablauf

DEBUG

(Sehr) detaillierte Informationen zu einzelnen Teilschritten

Der Log-Level DEBUG dient dazu, einzelne Detailabläufe nachzuvollziehen. Nachrichten, die unter diesem Level produziert werden, sind hauptsächlich zur Analyse von Fehlersituationen notwendig. Wird die jadice document platform auf diesen Level konfiguriert, so wird das durch die Anzeige eines entsprechenden Wasserzeichens auf der Viewer-Komponente verdeutlicht. Der Nutzer wird durch diese Meldung darauf hingewiesen, dass die Applikation umfangreiche Log-Meldungen ausgibt und daher nicht die reguläre Performance zu erwarten ist.

Für Produktivsysteme wird empfohlen, den Log-Level auf WARN zu setzen. So werden in Fehlerfällen die wichtigsten Informationen ausgegeben ohne dass im günstigen Fall die Performance beeinträchtigt wird.

Qualified LogEvents und Qualified Logging

Ereignisse, die ein gewissen Schweregrad darstellen oder die in integrierenden Anwendungen möglicherweise spezifische Reaktionen auslösen sollen, werden in jadice als Qualified Log-Events kategorisiert. Die jadice document platform propagiert diese Ereignisse an QualifiedLogListener Instanzen.

QualifiedLogEvents können in den Schweregraden WARN, ERROR und FATAL auftreten. Um QualifiedLogEvents zu erhalten, müssen Realisierungen von QualifiedLogListener an dem zentralen jadice Logging Mechanismus registriert werden. Dieser wird durch die Klasse LoggingConfiguration repräsentiert. Eine Instanz der LoggingConfiguration kann mithilfe der statischen Methode LoggingConfiguration.getGlobal() bezogen werden.

Die Registrierung eines Listeners kann für zwei unterschiedliche Gültigkeitsbereiche erfolgen: Es können entweder alle qualifizierten Log-Messages der genannten Levels angefordert werden oder nur solche, die auf dem zum Registrierungs-Zeitpunkt verwendeten Thread produziert werden.

Wird ein QualifiedLogListener benachrichtigt, so erfolgt dies in Form eines QualifiedLogEvents. Neben der tatsächlichen Nachricht, die auch variable Textfragmente enthalten kann, besitzt jedes QualifiedLogEvent eine eindeutige ID, die angibt um welchen Typ von Event es sich handelt. Tritt beispielsweise ein Fehler im Lesevorgang eines Formats auf, so könnte die ID allgemein darüber informieren, dass ein Lesefehler in einem Format auftrat, während die Log-Message zusätzlich den Namen des Formats angeben könnte. Diese Trennung erlaubt es, auf gleichartige Events in einer definierten Weise zu reagieren.

Die ID eines QualifiedLogEvents setzt sich zusammen aus zwei Teilen: Component ID und Event ID. Erstere bezeichnet den Teilbereich der jadice document platform, dem die Meldung entstammt. Letztere bezeichnet den Typ des Events. Die gesamte ID wird als String repräsentiert in welchem die beiden Teile durch einen Bindestrich (-) voneinander getrennt sind; weitere Bindestriche sind nicht enthalten. Die Trennung der ID in zwei Bestandteile hat den Vorteil, dass auf eine ganze Gruppe von Nachrichten in gleicher Weise reagiert werden kann. Die jadice document platform gruppiert beispielsweise solche Events, die im Zusammenhang mit dem Rendering auftreten, unter einer gemeinsamen Component ID. Über entsprechende Filterung und Anzeigelogik könnte somit bewirkt werden, dass sämtliche Warnungen oder Fehler, die im Zusammenhang mit dem Rendering auftreten, dem Nutzer in einer Message Box angezeigt werden.

Als Hilfestellung geben sämtliche Log-Messages, die an Instanzen von QualifiedLogListener verteilt werden, ihre vollständige ID auch auf der Konsole aus. Diese kann notiert und in einem QualifiedLogListener verwendet werden.

[jadice document platform Version 5.5.12.1: Dokumentation für Entwickler. Veröffentlicht: 2021-08-17]