Logging Intro
in Arbeit ...
Logging ist das Mitschreiben des Programm-Ablaufes bezüglich wichtiger Zustandsänderungen bzw. Ereignissen (Benutzer anlegen
, Konfigurationsdatei lesen
, Login, oder Dateizugriffsfehler, falsche Benutzereingabe, etc.).
Das "Level" ist stark von der Phase im "Lebenszyklus" einer Software-Komponente ab – während der Entwicklung oder bei Fehlerbereinigung sind detaillierte Informationen wichtig, im Produktionseinsatz sind nur wichtige Schritte und Probleme von Interesse. Zur Steuerung dieses Aspektes dient das Logging-Level
.
TODO: fortsetzen …
Bei fehlender Konfiguration wird die Konfigurationsdatei das SDK verwendet (aktuell in Temurin-17.x unter ${JDK_BASE_DIR}/conf/logging.properties
).
Diese setzt das Logging-Level global für Produktionslauf-Bedürfnisse auf: .level=INFO
– damit werden Meldungen der Level INFO
, WARNING
und SEVERE
ausgegeben, die "unteren" Level CONFIG
, FINE, FINER
, FINEST
werden unterdrückt.
Eine Änderung der globalen logging.properties
ist möglich, aber meist nicht sinnvoll oder nötig.
Stattdessen kann eine System Property java.util.logging.config.file
gesetzt werden, die eine benutzerdefinierte Konfigurationsdatei festlegt.
Das geschieht durch Übergabe der System-Property beim Programm-Aufruf als VM-Option (Virtual-Machine-Option) in der Form: -Djava.util.logging.config.file=./my-logging.properties
Auf der Kommandozeile könnte man schreiben (wenn aktuelles Arbeitsverzeichnis das Modul-Basisverzeichnis ist):
java -Djava.util.logging.config.file=./my-logging.properties -classpath
./target/Demo06-Junit-Log-1.0-SNAPSHOT.jar athtlw5.rx2223g2c.d06junitlog.Company
In IntelliJ muss in der Run-Configuration der aktuellen App die Zeile für VM-Options aktiviert werden:
Link "Modify options" klicken, im nun geöffneten Menü "Add VM options" anhaken.

1. Logging-Konfigurationsdatei
Eine Logging-Konfigurationsdatei (mit einigen Erläuterungs-Kommentaren), die sowohl auf die Konsole als auch in eine Datei ausgibt, könnte so aussehen:
##### Eigene Logging Konfiguration #####
## Raute am Zeilenanfang: Kommentarzeile, Leerzeilen werden ignoriert!!
## zur Nutzung: bei App-Aufruf VM-Arg.: java -Djava.util.logging.config.file=./logging.properties
## Am besten als erste Elemente nach 'public class Xxx {...':
## private static final Class<?> CL = java.lang.invoke.MethodHandles.lookup().lookupClass();
## private static final String CLN = CL.getSimpleName();
## private static final Logger LOG = Logger.getLogger(CL.getName());
## Methoden-Start: LOG.entering(CLN, "methodName(ArgTyp1,ArgTyp2,..),arg1,arg2,.."); // Level.FINER
## bei Exceptions: MyException ex = new MyException("die Msg");
## LOG.log(Level.SEVERE, ex.getMessage(), ex);
## throw ex;
## im Normalfall: LOG.info("die Msg");
## Methoden-Ende: LOG.exiting(CLN, "methodName(...)"); // Level.FINER
## handlers= java.util.logging.ConsoleHandler ... nur Konsolenausgabe
handlers= java.util.logging.FileHandler, java.util.logging.ConsoleHandler
## mögliche Levels: OFF,SEVERE,WARNING,INFO,CONFIG,FINE,FINER,FINEST(=niedrigster WERT),ALL ###
.level= INFO
## spezifisch für alles innerhalb dieses Packages (auch Subpackages): ###
my.base.pkg.level = ALL
my.base.pkg.subpkg1.level = WARNING
## ConsoleHandler benötigt zusätzliche Details-Obergrenze -
## ConsoleHandler.level: feinstes Level, das auf Console ausgegeben werden kann (am besten ALL):
java.util.logging.ConsoleHandler.level = ALL
java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
## Formatierung für SimpleFormatter (siehe Details in Javadoc) zu 'java.util.Formatter' -
## wie in System.out.format(..):
## %1$: Zeitpunkt, %2$: Source (Aufrufer, sonst Logger-Name), %3$: Logger-Name (selten noetig),
## %4$: Log Level, %5$: Log-Text, %6$: Stacktrace, wenn da
java.util.logging.SimpleFormatter.format = < %1$tF,%1$tT.%1$tL %4$7s: [%2$s] %5$s %6$s >%n
## oder: java.util.logging.SimpleFormatter.format = --> %1$tF,%1$tT.%1tL %4$7s: [%2$s] %5$s %6$s !!! Eigene Konfig !!!%n
## Die Nachrichten in eine Datei im Ordner 'logs' unter dem Modul-Verzeichnis schreiben
## ('TheAppName' ersetzen durch passenden Namen für die App):
java.util.logging.FileHandler.limit = 250000
java.util.logging.FileHandler.count = 10
java.util.logging.FileHandler.formatter = java.util.logging.SimpleFormatter
java.util.logging.FileHandler.pattern = ./logs/TheAppName_g%g-u%u.log
## Verfügbare Ersetzungssymbole (wie oben %g, %u):
## "/" the local pathname separator
## "%t" the system temporary directory
## "%h" the value of the "user.home" system property
## "%g" the generation number to distinguish rotated logs
## "%u" a unique number to resolve conflicts
## "%%" translates to a single percent sign "%"
#### END ####
1.1. Wichtige Voraussetzungen für Datei-Ausgabe
-
das Log-Verzeichnis (hier im Module-Basisverzeichnis
logs
) MUSS zuvorHÄNDISCH
angelegt werden, damit die Datei-Ausgabe funktioniert! -
Das aktuelle Arbeitsverzeichnis (von diesem aus gilt der relative Pfad in
java.util.logging.FileHandler.pattern = ./logs/demo06-junit-log-%u.log
) muss das Verzeichnislogs
enthalten!
In IntelliJ kann das aktuelle Arbeitsverzeichnis (im FeldWorking Directory
) in der jeweiligen Run-Configuration gesetzt werden. Es gibt dafür die spezielle Variable$MODULE_WORKING_DIR$
(oder man schreibt den Pfad explizit).
2. Logging Praxis
2.2. Loggen statt System.out.println(..)
Logging erfolg durch Aufruf von Methoden an der (üblicherweise per Klassen-Variable erzeugten) Logger-Instanz (oft möglichs kompakt LOG
genannt), die im enfachsten Fall den Namen des gewünschten Logging-Levels haben:
-
LOG.severe("die Log-Message");
-
LOG.warning("die Log-Message");
-
LOG.info("die Log-Message");
-
LOG.config("die Log-Message");
-
LOG.fine("die Log-Message");
-
LOG.finer("die Log-Message");
-
LOG.finest("die Log-Message");
Zwei für das Verstehen des Logik-Flusses sehr praktische Methoden sind:
-
LOG.entering(CLN, "methodenName", param1, param2, …);
… erzeugt Log-Eintrag mitLevel.FINER
param1, param2, … kann beliebig viele Werte enthalten oder auch komplett weggelassen werden. -
LOG.exiting(CLN, "methodenName(..)");
TODO: schreiben
2.3. Prüfung der Programm-Settings mit 'static inizializer'
public class DemoClass {
private static final Class<?> CL = java.lang.invoke.MethodHandles.lookup().lookupClass();
private static final String CLN = CL.getSimpleName();
private static final Logger LOG = Logger.getLogger(CL.getName());
public static final String PROP_JUL_CONF_FILE = "java.util.logging.config.file";
static { // a "static initializer" - running only once as soon as class is "activated"
// Check if 'assert ...' statements are enabled or not:
boolean assertsEnabled = false; // will remain in this state if no assertions
assert assertsEnabled = true; // if running because they are enabled, sets and returns true
System.out.format("!! ASSERTIONS %s enabled !!%n%n", (assertsEnabled ? "ARE" : "NOT"));
// Current Working Dir:
System.out.format("Current WorkDir: '%s'%n", System.getProperty("user.dir"));
//Logging:
java.util.Locale.setDefault(Locale.Category.DISPLAY, Locale.ROOT);
String julConfPropsPath = System.getProperty(PROP_JUL_CONF_FILE);
if (julConfPropsPath == null) {
System.out.format("INFO: SysProp '%s' not set, using standard logging settings%n%n",
PROP_JUL_CONF_FILE);
} else {
File julConfPropsFile = new File(julConfPropsPath);
if (!julConfPropsFile.isFile()) {
System.out.format("WARN: '%s' defined by SysProp '%s' does not exist or not a file, "
+ "so no logging shown at all!%n%n", julConfPropsPath, PROP_JUL_CONF_FILE);
} else {
try {
System.out.format("INFO: Logging config file '%s' used!%n%n",
julConfPropsFile.getCanonicalPath());
} catch (IOException e) {
System.out.format("ERR: Problem accessing logging config file '%s': %s",
julConfPropsPath, e.getMessage());
}
}
}
}
public static void main(String[] args) {
LOG.entering(CLN, "main", args);
// ...
LOG.exiting(CLN, "main(..)");
}
}
TODO: fertigstellen
3. LogManager
Es wird beim Laden der Klasse eine einzige Instanz (Singleton) der Klasse erzeugt. Diese Instanz kann nicht geändert werden.
Bei der Initialisierung der JVM wird der voll qualifizierte Name der zu verwendenden Klasse aus der System Property 'java.util.logging.manager'
entnommen (und kann auf diese Weise durch eine von java.util.logging.LogManager abgeleitete Klasse ersetzt werden!).
Er verwaltet:
-
eine "Namespace"-Hierarchie aller Logger-Objekte (diese werden darin verwaltet und können daraus abgerufen werden).
-
einen Satz von
java.util.Properties
(Key-Value-Paare), die zur Konfigutation der verschiedenen Loggong-Objekte dienen.
Man erhält diese Instanz mit der statischen Methode LogManager theLogMgr = LogManager.getLogManager()
Mit theLogManager.getLogger("the.fully.qualified.NameOfClass")
kann das aktuelle Logger-Objekt erhalten werden. Es kann sogar dynamisch die Konfoguration neu eingelesen werden.
Im einfachen Anwendungsfall wird der LogManager aber nicht explizit benötigt, er erledigt z.B. im Hintergrund die Konfiguration mittels der systemweiten Konfigurationsdatei (im JDK-conf-Verzeichnis) logging.properties
oder einer eigenen, per System Property java.util.logging.config.file
festgelegten Properties-Datei.
- Achtung
-
wenn die per
java.util.logging.config.file
festgelegte Datei nicht gefunden wird, erfolgt GAR KEIN Logging! Man sollte also bei Konfigurationsänderungen auf Logging-Ausgaben prüfen!
4. Doku zu den JUL-Framework-Klassen
4.2. Tutorials
https://www.vogella.com/tutorials/Logging/article.html [Java Logging API - Tutorial] … 2022-12-31
https://docs.oracle.com/en/java/javase/17/core/java-logging-overview.html [Java Logging Overview] … 2023-03-05
4.3. System.Logger
https://stackoverflow.com/questions/59976828/difference-between-java-util-logging-logger-and-java-lang-system-logger [Difference between java.util.logging.Logger and java.lang.System.Logger - Stack Overflow] … 2023-02-13
https://openjdk.org/jeps/264 [JEP 264: Platform Logging API and Service] … 2023-02-13
https://blog.frankel.ch/system-logger/ [System Logger] … 2023-02-13
https://dzone.com/articles/system-logger [Understanding System Logger - DZone] … 1.10.2023, 19:22:46
https://renato.athaydes.com/posts/java-system-logging [Renato Athaydes] … 1.10.2023, 19:25:43