~/home of geeks

Logging mit Tücken

· 1168 Wörter · 6 Minute(n) Lesedauer

Technikerin liest Logdateien.

Bei der Verwendung moderner Logging-Frameworks gibt es einige Fallstricke. Hier werden einige von ihnen beleuchtet und Lösungsansätze angeboten.

Java Logging Frameworks #

Die meisten Frameworks unterteilen sich in zwei Module: API und Implementierung.

Beispiel Log4j2 API -> Log4j2 Core


<dependency>
    <groupId>org.apache.logging.log4j</groupId>
    <artifactId>log4j-api</artifactId>
    <version>2.17.0</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.17.0</version>
</dependency>

API #

Hier wird die Schnittstelle definiert, welche der Entwickler in seinem Code verwenden kann. Die API ist üblicherweise unabhängig von der Implementierung und bietet eine einheitliche Schnittstelle. Frameworks, wie Log4J2 erlauben Platzhalter in den Lognachrichten, welche zur Laufzeit durch die übergebenen Parameter ersetzt werden. Dies hilft, die manchmal aufwändige Umwandlung von Objekten in Strings zu vermeiden.

Logger log = LoggerFactory.getLogger(MyClass.class);
String welt = "welt";
log.

info("Hallo {}",welt);

Implementierung #

Hier wird definiert, welche Auswirkungen das Logging hat. Das kann von Ausgaben auf der Konsole bis hin zu E-Mails, Einträgen in einer Datenbank, Einträge im Sysmon-System etc. alles Mögliche sein. Entwickler können auch ihren eigenen Anforderungen entsprechend Implementierungen erstellen.

Die Implementierung wird üblicherweise implementierungsspezifisch konfiguriert:

Ausschnitt aus einer Log4J2-Konfiguration, welche in eine Logdatei schreibt:


<RollingRandomAccessFile name="File" append="true" fileName="@log4j.file.path@${identifier}.log"
                         filePattern="@log4j.file.path@${identifier}-%d{yyyy-MM-dd}.log">
    <PatternLayout>
        <pattern>%d{DEFAULT} %p [%t] %c - %m%n</pattern>
    </PatternLayout>
    <Policies>
        <TimeBasedTriggeringPolicy/>
    </Policies>
</RollingRandomAccessFile>

Logging Facaden #

Es gibt Frameworks, die nur den API-Teil umsetzen. So ist z. B. Apache Commons Logging eine sogenannte Logging Facade.

Diese bestehen nur aus dem API-Teil und besitzen ausser einem einfachen Console-Logger keine Implementierung. Dafür kann an eine andere Implementierung/API delegiert werden (wie Log4J, SLF4J etc.)

SFL4J ist ebenfalls eine Logging Facade.

Wozu Logging Facaden? #

Sie kapseln die Implementierung, die verwendet wird, von dem entwickelten Kode. So werden Libraries Logging-Portabel.

Beispielsweise verwendet Spring eine eigene Variante von Apache Commons Logging (JCL), eine andere Library könnte die Log4J API verwenden. In der ausführenden Anwendung kann dann definiert werden, welche Implementierung zum Einsatz kommen soll.

Loggingfacaden und Auswahl der Implementierung
Loggingfacaden und Auswahl der Implementierung

Das setzt aber voraus, dass die Module nur eine API verwenden, keine Implementierung. In Maven kann man in den Modulen die Implementierungen mit dem scope provided einbinden. So hat man im Modul eine Implementierung, die man zum Testen verwenden kann, die aber nicht an andere Anwendungen exportiert werden, welche das Modul einbinden.

So landet die Implementierung nicht in den Dependencies der inkludierenden Anwendung. Auf diese Weise kann die Anwendung selber bestimmen, welche Implementierung sie verwendet.

Wird das nicht berücksichtigt, muss man in der Anwendung die Implementierungen excluden.

Excluden einer Implementierung
Excluden einer Implementierung

Beispiel einer Maven-Dependency-Definition, welche die commons-logging Implementierung von spring-context excludet und über slf4j-log4j12 und log4j die Implementierung von Log4J verwendet.


<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>3.1.2.RELEASE</version>
        <scope>runtime</scope>
        <exclusions>
            <exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>jcl-over-slf4j</artifactId>
        <version>1.7.0</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.0</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.7.0</version>
        <scope>runtime</scope>
    </dependency>
    <dependency>
        <groupId>log4j</groupId>
        <artifactId>log4j</artifactId>
        <version>1.2.14</version>
        <scope>runtime</scope>
    </dependency>
</dependencies>

Übersicht Java Logging Frameworks #

Die folgende Übersicht von 2023/2024 ist nicht umfassend, sondern zeigt nur die populärsten Frameworks und Facaden.

Apache Commons Logging (JCL)
Logging Facade / einfache Implementierung
Unterstützt Log4J, Avalon LogKit, JUL. SLF4J hat eigene Adapter für JCL.
Spring hat eine eigene Flavour von JCL (spring-jcl)
Einer der Ältesten
commons.apache.org/proper/commons-logging/

Java Logging API (java.util.logging – JUL)
Javas default Logging API und Implementierung in der JRE seit JDK 1.4
Wird unter anderem von Apache Tomcat benutzt
docs.oracle.com/javase/8/docs/api/java/util/logging/package-summary.html

Log4J1
Implementierung
logging.apache.org/log4j/1.x/

Log4j2 API
Logging Facade
Unterstützt Log4J2 Core, Logback, SLF4J, JUL, Log4J1
logging.apache.org/log4j/2.x/

Log4j2 Core
Implementierung
logging.apache.org/log4j/2.x/

Logback
Implementierung, wurde mal als Ersatz für Log4J entwickelt
logback.qos.ch/

SLF4J (Simple Logging Facade for Java)
Logging Facade
Unterstützt Log4J2, JUL, JCL, Logback.
www.slf4j.org

JBoss Logging
Logging Facade
Unterstützt JBoss Log Manager, Log4J2, SLF4J, Log4J1, JUL
Verwendung in Hibernate
github.com/jboss-logging/jboss-logging

JBoss Log Manager
Implementierung
github.com/jboss-logging/jboss-logmanager

Architektur SLF4J #

Das SLF4J-Framework besteht aus der SLF4J API, welche keine Implementierung hat. Sie besitzt dafür Bindings (eine Art Adapter) für andere Implementierungen.

Konkrete Bindings von SLF4J, aus www.slf4j.org/manual.html
Konkrete Bindings von SLF4J, aus www.slf4j.org/manual.html

Wenn man bei SLF4J mehr als ein Binding im Klassenpfad hat, meckert dies SLF4J in den ersten Logzeilen an:

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:.../slf4j-log4j12-1.7.21.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:.../logback-classic-1.1.7.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.slf4j.impl.Log4jLoggerFactory]

Wichtige Regel: Es darf nur eine Implementierung im Klassenpfad sein.

Wichtige Regel: Lies deine Logdateien, auch die ersten Zeilen.

Fehlt bei SLF4J ein Binding im Klassenpfad, meckert dies SLF4J in den ersten Logzeilen an:

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See https://www.slf4j.org/codes.html#StaticLoggerBinder for further details.

Wichtige Regel: Es muss mindestens eine Implementierung im Klassenpfad sein.

Architektur Log4J2 #

Das Log4J2-Framework besteht aus einer API, der Log4J2 API und einer Implementierung, dem Log4J2 Core, sowie Bridges (eine Art Adapter) für andere Facaden.

Übersicht der Briges von Log4J2, aus logging.apache.org/log4j/2.x/faq.html
Übersicht der Briges von Log4J2, aus logging.apache.org/log4j/2.x/faq.html

Ein Fehler, der bei der Verwendung von Log4J2 mit Bridges passieren kann, sind Logger-Schleifen. Man stelle sich die folgende Konfiguration vor:

Log4J2 API -> SLF4J API -> SLF4J Implemtierung = Log4J2 API

Dies passiert, wenn man die beiden Libraries

log4j-to-slf4j + log4j-slf4j-impl

im Klassenpfad hat. Dies verhält sich wie eine Endlosschleife und kann zu Stacktraces führen oder gar keine Ausgaben produzieren.

Wichtige Regel: Kenne deine Logging-Kette.

Default Strategien #

Die meisten Logging-Frameworks haben eine Default-Strategie, wenn keine Konfiguration gefunden wird. Wenn man mehrere Logging Implementationen im Klassenpfad hat, kann es je nach Facade dazu führen, dass eine der Implementierungen automatisch verwendet wird.

Beispiel JCL:

Die LogFactory prüft wie folgt nach der zu verwendenden Implementierung:

  1. Konfigurationsproperty org.apache.commons.logging.Log
  2. System property org.apache.commons.logging.Log
  3. Wenn Log4j im Klassenpfad ist, dieses verwendenden
  4. JUL (JDK 1.4 Logger)
  5. JCL eigenes Simple Logging

Wichtige Regel: Kenne das Default-Verhalten deiner Logger.

Mehrfache Implementierungen #

Wie sieht eine Implementierung für einen Adapter aus?

Implementierung von SLF4J zu Log4J2
Implementierung von SLF4J zu Log4J2

Der Log4J2 Adapter implementiert Klassen aus dem Paket org.slf4j.impl, welche von SLF4J verwendet werden. Sind bei solchen Konstellationen zwei Implementierungen im Klassenpfad (doppelte Klassen), kann dies zu undeterministischem Verhalten führen.

Wichtige Regel: Prüfe Klassenduplikate.

Wie man Klassenduplikate mit Maven prüfen kann, habe ich im Artikel Maven to the rescue: Duplikate und Maven beschrieben.

Lösungsstrategien #

Die folgende Liste soll helfen, die Fallstricke zu vermeiden:

  • Festlegen, wie meine Logger-Chain aussehen soll (Kenne deine Logging-Kette). Das umfasst auch alle von eingebundenen Libraries verwendeten Facaden und Implementierungen.
    • Nur eine Implementierung (Prüfe Klassenduplikate).
  • Maven Dependencies checken.
    • Nicht erwünschte Libraries, insbesondere Adapter und Implementationen excluden.
  • Default-Behaviour der eingesetzten Logger kennen (Kenne das Default-Verhalten deiner Logger).
  • Klassenduplikate checken (Prüfe Klassenduplikate).
  • Fehlermeldungen der Frameworks lesen (Lies deine Logdateien, auch die ersten Zeilen).

Was auch zu beachten ist: Wenn Module geschrieben werden, die in anderen Anwendungen eingebunden werden sollen, beachtet, dass die Implementierung des Loggings nicht in die Anwendung exportiert wird.

Beispielsweise habt ihr ein Modul “Datenbankzugriff”, welches Log4J2 verwendet. Wenn ihr das Modul in eine Anwendung einbindet, die Logback verwendet, sollte in den Dependencies die Log4J2-Core nicht auftauchen. Im Modul “Datenbankzugriff” könnt ihr die Log4J2-Core-Implementierung mit dem scope provided einbinden, so dass sie nur zum Testen des Moduls verwendet wird.