CodeGym /Kurse /JAVA 25 SELF /Formatierung und Log-Level: Best Practices

Formatierung und Log-Level: Best Practices

JAVA 25 SELF
Level 63 , Lektion 1
Verfügbar

1. Struktur einer Log‑Nachricht

Stellen wir uns vor, Logs sind nicht einfach nur ein „Gedankenstrom“ Ihrer Anwendung, sondern ein wertvolles Journal, in dem Sie oder Ihre Kollegin/Ihr Kollege in einem Monat oder Jahr die Antwort auf die Frage finden können: „Was zum Teufel ist hier passiert?“ Damit das möglich ist, muss jede Log‑Nachricht strukturiert sein. Üblicherweise (und das ist der Standard in den meisten Bibliotheken) enthält jede Nachricht:

  • Ereigniszeit – wann es passiert ist.
  • Level – wie wichtig es ist (INFO, ERROR usw.).
  • Logger‑Name – meist der Klassen‑ oder Komponentenname.
  • Nachrichtentext – was genau passiert ist.
  • Stacktrace (falls es einen Fehler gibt) – um zu verstehen, wo und warum.

Hier ist ein Beispiel für eine gut formatierte Logzeile (am Beispiel von Log4j/SLF4J):

2024-06-16 18:42:07,123 INFO  com.example.MainApp - Benutzer hat sich angemeldet: username=vasya

Und wenn ein Fehler passiert ist:

2024-06-16 18:42:10,456 ERROR com.example.LoginService - Fehler bei der Benutzeranmeldung: vasya
java.lang.IllegalArgumentException: Ungültiges Passwort
    at com.example.LoginService.checkPassword(LoginService.java:42)
    ...

Warum ist das wichtig?
Wenn eine Anwendung lange läuft, können Logs Gigabytes belegen. Sind die Nachrichten nicht strukturiert, wird die Fehlersuche zur Aufgabe auf dem Niveau „rate die Melodie am Lüftergeräusch“.

2. Formatierung von Nachrichten

Warum man es nicht so machen sollte:

logger.info("Benutzer " + username + " hat sich im System angemeldet");

Klingt simpel, hat aber einen Haken: Selbst wenn das Logging‑Level aktuell auf ERROR steht, wird der String in den Klammern trotzdem zusammengebaut (die Konkatenation läuft), und das sind unnötige Ressourcen. In großen Systemen, in denen pro Sekunde tausende Logzeilen entstehen, kann das zu spürbaren Verzögerungen führen.

Richtig: Platzhalter und Parameter

Moderne Bibliotheken (z. B. SLF4J und Log4j 2) unterstützen Platzhalter mit Parametern:

logger.info("Benutzer {} hat sich im System angemeldet", username);

Hier wird der String nur dann aufgebaut, wenn das aktuelle Logging‑Level die Ausgabe zulässt. Steht es z. B. auf WARN, erfolgt nicht einmal die String‑Berechnung – Ressourcen und Nerven werden geschont.

Bonus: Übergeben Sie mehrere Parameter, werden sie der Reihe nach eingesetzt:

logger.info("Benutzer {} hat die Aktion {} am Objekt {} ausgeführt", username, action, objectId);

Exceptions loggen (Stacktrace)

Wenn Sie eine Exception abfangen, hängen Sie den Stacktrace nicht manuell an die Nachricht:

// NICHT SO:
logger.error("Fehler: " + ex.getMessage() + "\n" + Arrays.toString(ex.getStackTrace()));

Richtig:

logger.error("Fehler bei der Verarbeitung der Anfrage", ex);

SLF4J und Log4j fügen den Stacktrace automatisch sauber zum Log hinzu.

Beispiel: Vergleich der Ansätze

// Schlecht (Konkatenation wird immer ausgeführt)
logger.debug("Objekt: " + expensiveToString(obj));

// Gut (Lazy-Auswertung)
logger.debug("Objekt: {}", obj);

3. Auswahl der Log‑Level

Wenn bei Ihnen im Log alles auf ERROR steht, sind das keine Logs mehr, sondern eine „rote Lampe“. Wenn alles auf DEBUG steht, gehen Sie in Details unter. Schauen wir uns an, wann welches Level zu verwenden ist.

Level Verwendungszweck Beispielnachricht
ERROR
Kritische Ausfälle, durch die das System falsch funktioniert oder gar nicht läuft „Fehler bei der Verbindung zur Datenbank“
WARN
Wichtige Warnungen, nicht kritisch, aber beachtenswert „Benutzer nicht gefunden, verwende guest“
INFO
Reguläre Ereignisse, die den normalen Betrieb widerspiegeln „Benutzer registriert: vasya“
DEBUG
Detailinformationen für das Debugging, nicht für den Produktivbetrieb „Methode checkPassword mit Parametern aufgerufen …“
TRACE
Maximale Detailtiefe, üblicherweise für tiefgehende Diagnosen „Beginn der Verarbeitungsschleife: i=0“

Typische Nachrichtenbeispiele

  • ERROR – Datei konnte nicht geschrieben werden, unbehandelte Exception abgefangen, Dienst nicht verfügbar.
  • WARN – veraltete API, verdächtiges Nutzerverhalten, Versuchs‑Limit überschritten.
  • INFO – Benutzer ein-/ausgeloggt, Auftragsverarbeitung abgeschlossen, Start der Anwendung.
  • DEBUG – Request‑Parameter, Variablenwerte, Zwischenergebnisse von Berechnungen.
  • TRACE – Ein-/Ausstieg in Methoden, innere Schleifen, Details zur Arbeitsweise von Algorithmen.

Tipp:
Im Produktivbetrieb sind üblicherweise nur INFO und höher aktiv, manchmal WARN und ERROR. DEBUG und TRACE – nur bei der Suche nach schwierigen Bugs.

4. Best Practices (bewährte Logging‑Praktiken)

Keine sensiblen Daten loggen

Passwörter, Tokens, Kreditkartennummern – all das gehört nicht in Logs. Auch wenn es so wirkt, als sei die Logdatei „nur für mich“, denken Sie an die DSGVO und die Kollegin/den Kollegen, die/der den Log versehentlich in den Gruppenchat schickt.

// Schlecht:
logger.info("Benutzer {} hat sich mit dem Passwort {} angemeldet", username, password);

// Gut:
logger.info("Benutzer {} hat sich im System angemeldet", username);

ERROR‑Level nicht überstrapazieren

Wenn Sie alles pauschal über logger.error schreiben, wird bei einer echten Katastrophe niemand sie bemerken – alle haben sich an die „roten Lampen“ gewöhnt. Verwenden Sie ERROR nur für Situationen, in denen die Anwendung wirklich nicht fortfahren kann oder die Business‑Logik verletzt ist.

Exceptions mit vollständigem Stack loggen

Schreiben Sie nicht nur ex.getMessage(), sonst erfahren Sie nie, wo der Fehler genau auftrat. Geben Sie die Exception als zweiten Parameter an den Logger.

logger.error("Fehler bei der Verarbeitung der Anfrage", ex);

Eindeutige IDs verwenden (Ereigniskorrelation)

In großen Systemen ist es hilfreich, jeder Anfrage, jedem Benutzer oder jeder Operation eine eindeutige ID zuzuweisen. Das hilft, Ereignisse aus verschiedenen Teilen des Systems zu „verknüpfen“.

logger.info("Bearbeitung der Bestellung gestartet: orderId={}", orderId);
logger.info("Bestellung erfolgreich verarbeitet: orderId={}", orderId);

Nicht alles wahllos loggen

Wenn es zu viele Logs gibt, werden sie nutzlos. Loggen Sie nicht jede Codezeile, sonst ist die relevante Information unauffindbar.

Nachrichten verständlich formatieren

Formulieren Sie Nachrichten so, dass sie nicht nur die Autorin/der Autor des Codes versteht, sondern auch die Person, die die Logs in sechs Monaten liest. Vermeiden Sie Abkürzungen, undurchsichtige Kürzel und „Insider‑Witze“.

5. Praxis: Logformat und Level konfigurieren

Beispiel für eine Log4j2‑Format‑Konfiguration (log4j2.xml)

<Configuration>
  <Appenders>
    <Console name="Console" target="SYSTEM_OUT">
      <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5level %logger{36} - %msg%n"/>
    </Console>
  </Appenders>
  <Loggers>
    <Root level="info">
      <AppenderRef ref="Console"/>
    </Root>
  </Loggers>
</Configuration>

Was bedeutet das?

  • %d{...} – Zeit des Ereignisses.
  • %-5level – Level (ERROR, INFO usw.).
  • %logger{36} – Logger‑Name (meist die Klasse).
  • %msg – die eigentliche Nachricht.

Beispielcode mit verschiedenen Log‑Leveln (SLF4J)

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class LogDemo {
    private static final Logger logger = LoggerFactory.getLogger(LogDemo.class);

    public static void main(String[] args) {
        logger.info("Anwendung gestartet");
        logger.debug("Wert der Variablen x: {}", 42);

        try {
            throw new IllegalArgumentException("Au weia!");
            // ...
        } catch (Exception ex) {
            logger.error("Beim Start ist ein Fehler aufgetreten", ex);
        }
    }
}

Demonstration der Unterschiede zwischen den Leveln

Wenn im Logger‑Setup das Level INFO eingestellt ist, werden Nachrichten mit dem Level DEBUG und niedriger nicht ausgegeben. Stellen Sie das Level in der Konfiguration auf debug um – Sie sehen die Details.

6. Typische Fehler

Fehler Nr. 1: String‑Konkatenation in Logs. Sehr häufig schreiben Einsteigerinnen/Einsteiger so:

logger.debug("Benutzer: " + user.getName() + ", Rolle: " + user.getRole());

Dadurch werden die Strings auch bei deaktiviertem DEBUG-Level aufgebaut – das führt zu unnötiger Last. Verwenden Sie Platzhalter!

Fehler Nr. 2: Logging ohne Exception‑Stack. Es wird nur die Nachricht geloggt:

logger.error("Fehler: " + ex.getMessage());

Am Ende fehlt im Log die Information, wo der Fehler auftrat. Geben Sie die Exception als zweiten Parameter mit!

Fehler Nr. 3: Alles wahllos auf Level ERROR loggen. Wenn alles rot ist, ist nichts rot. Verwenden Sie die Level zweckgerecht, sonst gehen wichtige Fehler im „Kleinkram“ unter.

Fehler Nr. 4: Sensible Daten loggen. Schreiben Sie niemals Passwörter, Tokens oder Kartennummern in Logs. Auch wenn es so scheint, als würde das niemand sehen – das Leben liebt Überraschungen.

Fehler Nr. 5: Unverständliche Nachrichten. Wenn eine Lognachricht wie „ERR42: fail“ aussieht, werden Sie sich in einem Monat selbst nicht mehr erinnern, was das bedeutet. Schreiben Sie klar und aussagekräftig.

Fehler Nr. 6: Fehlende eindeutige Kennungen. In komplexen Systemen werden Sie ohne orderId, userId und andere IDs Ereignisse nicht korrelieren können und nicht nachvollziehen, was mit einer konkreten Benutzerin/einem konkreten Benutzer oder einer konkreten Bestellung passiert ist.

Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION