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.

IJ-RunConf-ModifyOptAddVmOptions

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 zuvor HÄ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 Verzeichnis logs enthalten!
    In IntelliJ kann das aktuelle Arbeitsverzeichnis (im Feld Working 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.1. Nutzen Standard-Konfiguration, eigene Konfiguration

TODO: schreiben

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 mit Level.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

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