15.9 Logging mit Java
Das Loggen (Protokollieren) von Informationen über Programmzustände ist ein wichtiger Teil, um später den Ablauf und die Zustände von Programmen rekonstruieren und verstehen zu können. Mit einer Logging-API lassen sich Meldungen auf die Konsole oder in externe Speicher wie Text- bzw. XML-Dateien und Datenbanken schreiben oder über einen Chat verbreiten.
15.9.1 Logging-APIs
Bei den Logging-Bibliotheken und APIs ist die Java-Welt leider gespalten. Da die Java-Standardbibliothek in den ersten Versionen keine Logging-API anbot, füllte die Open-Source-Bibliothek log4j schnell diese Lücke. Sie wird heute in nahezu jedem größeren Java-Projekt eingesetzt. Als in Java 1.4 die Logging-API (JSR 47) einzog, war die Java-Gemeinde erstaunt, dass java.util.logging (JUL) weder API-kompatibel mit dem beliebten log4j noch so leistungsfähig wie log4j ist.[ 246 ](Die Standard Logging-API ist dagegen nur ein laues Lüftchen, das nur Grundlegendes wie hierarchische Logger bietet. An die Leistungsfähigkeit von log4j mit einer großen Anzahl von Schreibern in Dateien, Syslog-/NT-Logger, Datenbanken, Versand über das Netzwerk kommt das Standard-Logging nicht heran. )
Im Laufe der Jahre veränderte sich das Bild. Während in der Anfangszeit Entwickler ausschließlich auf log4j bauten, werden es langsam mehr Projekte mit der JUL. Ein erster Grund ist der, dass einige Entwickler externe Abhängigkeiten vermeiden wollen (wobei das nicht wirklich funktioniert, da nahezu jede eingebundene Java-Bibliothek selbst auf log4j baut). Der zweite Grund ist, dass für viele Projekte JUL einfach reicht. In der Praxis bedeutet dies für größere Projekte, dass mehrere Logging-Konfigurationen das eigene Programm verschmutzen, da jede Logging-Implementierung unterschiedlich konfiguriert wird.
15.9.2 Logging mit java.util.logging
Mit der Java-Logging-API lässt sich eine Meldung schreiben, die sich dann zur Wartung oder zur Sicherheitskontrolle einsetzen lässt. Die API ist einfach:
package com.tutego.insel.logging;
import static java.time.temporal.ChronoUnit.MILLIS;
import static java.time.Instant.now;
import java.time.Instant;
import java.util.logging.Level;
import java.util.logging.Logger;
public class JULDemo {
private static final Logger log = Logger.getLogger( JULDemo.class.getName() );
public static void main( String[] args ) {
Instant start = now();
log.info( "Wir starten mit JUL" );
try {
log.log( Level.INFO, "In {0} Sekunden geht es los", 0 );
throw null; // Fehler erzeugen
}
catch ( Exception e ) {
log.log( Level.SEVERE, "Oh Oh", e );
}
log.info( () -> String.format( "Laufzeit %s ms", start.until( now(), MILLIS ) ) );
}
}
Lassen wir das Beispiel laufen, folgt auf der Konsole die Warnung:
Mai 21, 2017 10:24:57 NACHM. com.tutego.insel.logging.JULDemo main
INFORMATION: Wir starten mit JUL
Mai 21, 2017 10:24:57 NACHM. com.tutego.insel.logging.JULDemo main
INFORMATION: In 0 Sekunden geht es los
Mai 21, 2017 10:24:57 NACHM. com.tutego.insel.logging.JULDemo main
SCHWERWIEGEND: Oh Oh
java.lang.NullPointerException
at com.tutego.insel.logging.JULDemo.main(JULDemo.java:19)
Mai 21, 2017 10:24:57 NACHM. com.tutego.insel.logging.JULDemo main
INFORMATION: Laufzeit 131 ms
Das Logger-Objekt
Zentral ist das Logger-Objekt, das über Logger.getAnonymousLogger() oder über Logger.getLogger(String name) geholt werden kann, wobei name in der Regel mit dem voll qualifizierten Klassennamen belegt ist. Oft ist der Logger als private statische finale Variable in der Klasse deklariert.
Loggen mit Log-Level
Nicht jede Meldung ist gleich wichtig. Einige sind für das Debuggen oder wegen der Zeitmessungen hilfreich, doch Ausnahmen in den catch-Zweigen sind enorm wichtig. Damit verschiedene Detailgrade unterstützt werden, lässt sich ein Log-Level festlegen. Er bestimmt, wie »ernst« der Fehler bzw. eine Meldung ist. Das ist später wichtig, wenn die Fehler nach ihrer Dringlichkeit aussortiert werden. Die Log-Level sind in der Klasse Level als Konstanten deklariert:[ 247 ](Da das Logging-Framework in Version 1.4 zu Java stieß, nutzt es noch keine typisierten Aufzählungen, denn die gibt es erst seit Java 5. )
FINEST (kleinste Stufe)
FINER
FINE
CONFIG
INFO
WARNING
SEVERE (höchste Stufe)
Zum Loggen selbst bietet die Logger-Klasse die allgemeine Methode log(Level level, String msg) bzw. für jeden Level eine eigene Methode:
Level | Aufruf über log(…) | Spezielle Log-Methode |
---|---|---|
SEVERE | log(Level.SEVERE, msg) | severe(String msg) |
WARNING | log(Level.WARNING, msg) | warning(String msg) |
INFO | log(Level.INFO, msg) | info(String msg) |
CONFIG | log(Level.CONFIG, msg) | config(String msg) |
FINE | log(Level.FINE, msg) | fine(String msg) |
FINER | log(Level.FINER, msg) | finer(String msg) |
FINEST | log(Level.FINEST, msg) | finest(String msg) |
Alle diese Methoden setzen eine Mitteilung vom Typ String ab. Sollen eine Ausnahme und der dazugehörende Strack-Trace geloggt werden, müssen Entwickler zu folgender Logger-Methode greifen, die auch schon das Beispiel nutzt:
void log(Level level, String msg, Throwable thrown)
Die Varianten von severe(…), warning(…) usw. sind nicht überladen mit einem Parametertyp Throwable.