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.
Das jadice Logging-Framework erlaubt ein direktes und einfaches Einbinden der bekanntesten und verbreitetsten Logging-Systeme und -Frameworks, wie JDK Logging, 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.
Die Auslieferung der jadice document platform stellt neben dem Standard JDK Logger drei weitere Implementationen von Logging-Delegates zur Verfügung. Soll Log4J2 verwendet werden, fügen Sie die entsprechende 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.
Die Implementation des Logging-Delegates für Log4J2 entspricht folgender Namenskonvention:
logging-log4j2-
version
.jar
In der Auslieferung finden Sie das entsprechende Log4J2-Delegate, jeweils passend zu den im Einsatz befindlichen jadice Modulen, unter folgender Verzeichnisstruktur:
lib/logging
Fügen Sie bitte den zu Ihrer jadice Version passenden Log4J2-Delegate sowie auch Log4J2 selbst in den Klassenpfad der Anwendung ein. Ist eine log4J-Konfiguration bereits auf dem Klassenpfad verfügbar, wird keine weitere Konfiguration benötigt.
Log4J2 wie auch detaillierte Informationen zur Konfiguration finden Sie auf der Log4J Homepage. Die zugehörigen Manuals finden Sie unter Log4J2 Manual.
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-
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.
version
.jar
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:
-
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. -
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. Zusätzlich zu den dort beschriebenen Ursachen, kann diese Fehlermeldung auftreten, wenn es zu Versionskonflikten kommt. Wenn kein Maven mit BOM verwendet wird, sondern slf4j und log4j jars anderweitig eingebunden werden, z. B. mittels gradle, ist außerdem darauf zu achten, dass dieselben Versionsnummern verwendet werden, die auch im viewer enthalten sind. Diese können beispielsweise im jadice-documentplatform-dist-package im Ordner lib/logging nachgesehen werden.
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.
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.
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.
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.
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.
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.
QualifiedLogEvent
s können in den Schweregraden
WARN
, ERROR
und
FATAL
auftreten. Um QualifiedLogEvent
s 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 QualifiedLogEvent
s. 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 QualifiedLogEvent
s 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.