CodeGym /Kurse /Java Server /Protokollierung externer Aufrufe und Ausnahmen

Protokollierung externer Aufrufe und Ausnahmen

Java Server
Level 21, Lektion 3
Verfügbar

1. Externe Aufrufe und Ausnahmen: Backend-typische Mysterien

Die Skala debug/info/warn/error haben wir bereits ausgebreitet. Nun wenden wir sie auf den Bereich an, wo ohne sie am schnellsten Schmerz beginnt: bei ausgehenden HTTP-Aufrufen und Ausnahmen.

Wenn eine Anwendung nur innerhalb eines Prozesses lebt und nicht nach außen geht, sind ihre Probleme normalerweise lokal: falsche Logik, NullPointerException, „der falsche Index in der Liste“. Aber sobald wir einen externen HTTP-Aufruf tätigen (und der ReadLater Starter tut dies in der Client-Phase), entsteht eine neue Welt voller Überraschungen: Das Netzwerk kann träge sein, der Dienst kann nicht verfügbar sein, DNS kann nicht aufgelöst werden, und die externe API kann anders antworten, als Sie erwartet haben.

Und hier beginnt der Klassiker: Der Benutzer sieht „nichts funktioniert“, und der Entwickler sieht… Stille. Die Logs werden zu Ihrer „Black Box“ (wie beim Flugzeug, nur ohne dramatische Musik). Gutes Loggen externer Aufrufe beantwortet einfache Fragen: Wohin wurde gegangen, was wurde gewollt, wie endete es, wie lange hat es gedauert. Gutes Loggen von Ausnahmen beantwortet eine weitere wichtige Frage: Warum ist es kaputtgegangen, und nicht nur was kaputtgegangen ist.

Um die Logs nicht in eine „Textwand“ zu verwandeln, ist es wichtig, zwei Ideen im Kopf zu behalten. Erstens: Wir protokollieren genau das, was der Diagnose hilft, und nicht alles willkürlich „für alle Fälle“. Zweitens: Wir halten sichere Grenzen ein – stopfen nicht das in die Logs, was sensibel oder riesig sein könnte, denn Logs leben fast immer länger als geplant und werden von mehr Menschen gelesen, als einem lieb ist.

2. Pass des externen Aufrufs: Minimum an Logs

Ein externer HTTP-Aufruf hat eine unangenehme Eigenschaft: Er kann so „abstürzen“, dass Sie nicht einmal eine HttpResponse erhalten. Das heißt, manchmal haben Sie einen Statuscode und einen Antwortkörper, manchmal nur eine Ausnahme (z.B. IOException oder Timeout). Daher ist es praktisch, die Protokollierung eines externen Aufrufs als kleines Szenario aus zwei (manchmal drei) Punkten aufzubauen: Start des Aufrufs, Ende des Aufrufs und ein separater Zweig für Fehler/Ausnahmen.

Stellen Sie sich vor, Sie öffnen das Log eine Woche nach dem Schreiben des Codes (und nach einer Woche wird jeder Code zu „von jemand anderem geschrieben“, das ist normal). Sie möchten in einer Zeile mindestens sehen: welches Szenario (catalog search oder catalog details), wohin genau gegangen wurde (ohne unnötige Geheimnisse), welches Ergebnis erzielt wurde (Status oder Fehlertyp) und wie viele Millisekunden es gedauert hat. Das ist der „Pass des Aufrufs“.

Unten folgt eine praktische Tabelle als Spickzettel. Dies ist kein „strenger Standard des Universums“, aber ein gutes Minimum für unser Projekt:

Was wir verstehen wollen Was ins Log aufnehmen Beispiel Feld/Wert
Was gemacht wurde Name der Operation operation=catalog.search
Wohin gegangen wurde Sichere URI-Form uri=https://openlibrary.org/search.json
Womit gegangen wurde Identifikator/Kriterium (vorsichtig!) query=clean code oder
externalId=OL123M
Womit endete es HTTP-Status oder Fehlertyp status=200 oder
failure=timeout
Wie lange es dauerte Dauer in ms durationMs=1370
In welchem Modus real /
mock
(falls zutreffend)
mode=real

Beachten Sie die Feinheit: „Womit gegangen wurde“ ist oft eine Benutzereingabe. In unserem Kurs kann dies eine Buchsuchanfrage (query) oder eine externe Buch-ID (externalId) sein. Im Lernprojekt ist das kein „Geheimnis“, aber im echten Leben können solche Daten durchaus personenbezogene Daten enthalten. Daher ist die Gewohnheit einfach: Wir protokollieren solche Werte bewusst, nicht automatisch, und protokollieren nicht das, was definitiv nicht protokolliert werden darf (Token, Passwörter, private Schlüssel).

3. Beispiel mit ReadLater Starter: nützliche Logs

Der verständlichste Weg, korrektes Loggen zu „spüren“, ist ein Blick auf den Code, der einen externen Aufruf tätigt. Im ReadLater Starter ist dies normalerweise die Schicht catalog.client, die über HttpClient auf das externe Buchverzeichnis zugreift. Wir werden hier nicht die Architektur umbauen (sie ist bereits in den vorherigen Tagen entstanden), sondern die richtigen Logpunkte hinzufügen.

Hier ist es wichtig, die beiden Ebenen nicht zu vermischen. Auf INFO-Level, in service oder app, lebt die Tatsache des Szenarios catalog search oder catalog details. Innerhalb des CatalogClient verbleiben die Transportdetails des HTTP-Aufrufs: URI, Status, Dauer. Es ist praktisch, sie auf DEBUG zu halten und punktuell zu aktivieren, wenn ein Problem analysiert wird. Dies löst auch die Verwirrung zwischen der Dauer des gesamten Szenarios und der Dauer eines bestimmten HTTP-Round-Trips.

Wichtig: Wir protokollieren nicht „alles“, sondern die Schlüsselmomente. Ein externer Aufruf kann als Mini-Kette dargestellt werden: URI vorbereitet → Anfrage gesendet → Antwort erhalten → Status/Fehler ausgewertet. Es ist nützlich, entweder die „Grenzen“ (vor und nach send()) zu loggen oder die „Tatsache“ des Abschlusses (Status/Dauer). Wenn man jede Zeile loggt, erhält man keine Diagnose, sondern einen literarischen Roman, den niemand liest.

Start/Ende des Aufrufs + Dauer: Grundgerüst

Unten ein kleines Beispiel eines „korrekten Grundgerüsts“. Es zeigt drei Dinge: Wir protokollieren, wohin wir gehen (in sicherer Form), messen die Dauer und protokollieren das Ergebnis (Status + Dauer). Beachten Sie: Wir verwenden System.nanoTime() – zum Messen der Dauer ist dies praktischer und genauer als currentTimeMillis().

import java.net.URI;

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

public class CatalogCallLoggerExample {
    private static final Logger log = LoggerFactory.getLogger(CatalogCallLoggerExample.class);

    public void logCall(URI uri, int statusCode, long startedAtNanos) {
        // Dauer messen wir ueber nanoTime: monotone Uhr, praktisch fuer Messungen
        long durationMs = (System.nanoTime() - startedAtNanos) / 1_000_000;

        // Auf der Transportgrenze ist dies DEBUG: Status und Dauer eines bestimmten HTTP-Aufrufs
        log.debug("Catalog HTTP response received, uri={} statusCode={} durationMs={}", safeUri(uri), statusCode, durationMs);
    }

    private String safeUri(URI uri) {
        // Im Log lassen wir nur scheme/host/path: query-String kann Benutzereingaben/Geheimnisse enthalten
        return uri.getScheme() + "://" + uri.getHost() + uri.getPath();
    }
}

Auf Client-Ebene ist dies der DEBUG-Pass eines erfolgreichen Aufrufs. Und Sie möchten, dass die URI im Log sicher ist: ohne vollständigen Query-String, ohne Token und ohne versehentliches Durchsickern von „allem, was der Benutzer eingegeben hat“.

Einbinden in einen realen Aufruf: vor send() und nach send()

Nun kommen wir näher an die eigentliche Stelle – den Code, der HttpClient aufruft. Wir werden nicht den gesamten CatalogClient mit 100 Zeilen schreiben (das Loggen soll im Beispiel nicht untergehen), sondern nur den entscheidenden Teil der Methode zeigen.

Aus Sicht des CatalogClient sind beide folgenden Meldungen auf Transport-Level DEBUG.

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;

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

public class OpenLibraryCatalogClient {
    private static final Logger log = LoggerFactory.getLogger(OpenLibraryCatalogClient.class);

    private final HttpClient httpClient;

    public OpenLibraryCatalogClient(HttpClient httpClient) {
        this.httpClient = httpClient;
    }

    public HttpResponse<String> call(URI uri) throws Exception {
        // Timer vor dem Senden der Anfrage starten, um die vollstaendige Dauer des externen Aufrufs zu sehen
        long startedAt = System.nanoTime();

        // Erstes Log: vorbereiteten Transport-Aufruf festhalten
        log.debug("Catalog HTTP request prepared, method=GET uri={}", safeUri(uri));

        // Anfrage separat zusammenbauen: so lassen sich bei Bedarf leichter Header/Timeouts hinzufuegen
        HttpRequest request = HttpRequest.newBuilder(uri).GET().build();

        // Hier kann eine Ausnahme auftreten (timeout/IOException), daher ist dieser Bereich eine entscheidende Grenze
        HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

        // Zweites Log: Ergebnis und Dauer eines bestimmten HTTP-Round-Trips festhalten
        long durationMs = (System.nanoTime() - startedAt) / 1_000_000;
        log.debug("Catalog HTTP response received, method=GET uri={} statusCode={} durationMs={}", safeUri(uri), response.statusCode(), durationMs);

        return response;
    }

    private String safeUri(URI uri) {
        // Query-Parameter entfernen: sie enthalten oft Benutzereingaben oder Geheimnisse
        return uri.getScheme() + "://" + uri.getHost() + uri.getPath();
    }
}

Hiermit ergeben zwei DEBUG-Meldungen bereits ein sehr gutes Minimum für die Transportebene. Die erste hilft zu verstehen, „wohin wir gegangen sind“ (und dies ist kritisch, wenn Sie eine konfigurierbare baseUrl haben: falsch konfiguriert – und die Anwendung geht ins Leere). Die zweite hilft zu verstehen, „wie es endete“ und „wie lange es dauerte“.

Und ja, man möchte es noch „schöner“ machen: den Vorgang (catalog.search) und eventuell query oder externalId mitschreiben. Das kann später hinzugefügt werden, wenn Sie entschieden haben, welche Felder wirklich nötig sind. Aber die grundlegende Disziplin ist bereits da.

4. Nicht-2xx und erwartete Fehler: Log-Level

Ein externer Dienst kann nicht nur 200 antworten. Manchmal sagt er ehrlich 404 (nicht gefunden), 429 (zu viele Anfragen), 500 (bei ihm ist alles schlecht) – und das sind verschiedene Geschichten. Wenn Sie anfangen, jede 404 als error zu loggen, erhalten Sie eine „Feuersirene“, die selbst dann heult, wenn alles normal ist.

Für unser Projekt ist dies besonders wichtig: Ein leeres Suchergebnis oder „nicht gefunden nach externalId“ ist oft ein normaler Geschäftsausgang, kein Systemausfall. Ja, für das Client-Szenario ist es unangenehm, „nicht gefunden“ zu erhalten, aber das ist nicht immer eine „Katastrophe auf ERROR-Niveau“. Daher sollten die Level sinnvoll gewählt werden: info für normale Ausgänge, warn für verdächtige Verschlechterungen, error für echte Fehler (Ausnahme, Unmöglichkeit, die versprochene Aktion auszuführen).

Unten eine kleine Vorlage: Wenn der Status nicht 2xx ist, schweigen wir nicht, schreien aber auch nicht immer error. Der Einfachheit halber ist die Regel grob, aber bereits nützlich: 4xx loggen wir als warn (weil es ein „Problem der Anfrage/Daten“ ist), und 5xx als error (weil es ein „Problem des Dienstes“ ist).

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

public class StatusLoggingExample {
    private static final Logger log = LoggerFactory.getLogger(StatusLoggingExample.class);

    public void logNon2xx(int statusCode, String operation) {
        // 5xx – Problem des externen Dienstes: das sieht bereits nach einem echten Fehler aus
        if (statusCode >= 500) {
            log.error("External service failed: operation={} status={}", operation, statusCode);
            return;
        }

        // 4xx – oft Problem der Daten/Anfrage: zumindest festhalten, aber ohne 'Feuersirene'
        if (statusCode >= 400) {
            log.warn("External service returned client error: operation={} status={}", operation, statusCode);
        }
    }
}

Im echten ReadLater Starter können Sie die Regel verfeinern: z.B. kann 404 für catalog details info sein („Buch mit dieser ID existiert nicht“), und 429 kann warn sein („wir werden eingeschränkt“). Aber wichtiger ist etwas anderes: Logs sollten die Wahrheit sagen und nicht ohne Grund in Panik geraten.

5. Protokollierung von Ausnahmen: Kontext und Stack Trace

Wenn eine Ausnahme auftritt, macht ein Anfänger oft eine von zwei Sachen. Erstens: Er ruft e.printStackTrace() auf und hofft, dass „da ja alles zu sehen ist“. Zweitens: Er loggt nur e.getMessage() und verliert den Stack Trace (d.h. er verliert einen Großteil des technischen Werts). Beide Fehler führen später zu ewigen Rätseln: „Wo genau ist es gefallen?“ und „Warum?“

Die richtige Gewohnheit in SLF4J ist einfach: Wenn Sie einen Fehler loggen, übergeben Sie das Exception-Objekt als letzten Parameter. Dann gibt der Logger selbst den Stack Trace aus (und tut dies formal korrekt). Gleichzeitig fügen Sie Kontext hinzu: welches Szenario, welche URI, welche query oder externalId, welche Einstellungen Einfluss gehabt haben könnten.

Vergleichen wir „schlecht“ und „normal“.

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

public class ExceptionLoggingExample {
    private static final Logger log = LoggerFactory.getLogger(ExceptionLoggingExample.class);

    public void bad(Exception e) {
        // Schlechte Praxis: Strings verketten und nur message loggen - Stack Trace geht verloren
        log.error("Catalog HTTP call failed: " + e.getMessage()); // verlieren Stack Trace
    }

    public void good(String query, Exception e) {
        // Gute Praxis: Kontext ueber placeholders + exception selbst als letztes Argument
        log.error("Catalog HTTP call failed, query={}", query, e); // Kontext + Stack Trace
    }
}

Die zweite Variante gewinnt aus zwei Gründen. Erstens: Sie behalten den Aufruf-Stack: Sie sehen die Stelle, an der das Problem tatsächlich aufgetreten ist. Zweitens: Sie haben einen Geschäftskontext: Was wir versucht haben zu tun (z.B. Bücher nach Anfrage finden).

Beispiel vollständiges try/catch: Dauer und Kontext

Das praktischste Muster ist, die Dauer selbst im Fehlerfall zu messen und alles an einer Stelle zu loggen. Dies ist besonders nützlich bei Timeouts: Sie möchten sehen, ob die Anfrage nach 1000ms oder nach 10ms abgebrochen wurde.

import java.net.URI;

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

public class TryCatchTemplate {
    private static final Logger log = LoggerFactory.getLogger(TryCatchTemplate.class);

    public void callExternal(URI uri) throws Exception {
        // Timer vor try starten: wird sowohl fuer den Erfolgsfall als auch fuer den catch benoetigt
        long startedAt = System.nanoTime();

        try {
            // Vorbereiteten Transport-Aufruf festhalten
            log.debug("Catalog HTTP request prepared, uri={}", safeUri(uri));

            // httpClient.send(...) hier
        } catch (Exception e) {
            // Auch bei Fehler Dauer berechnen: sehr wichtig fuer die Diagnose von Timeouts
            long durationMs = (System.nanoTime() - startedAt) / 1_000_000;

            // Exception als separaten Parameter uebergeben, damit der Logger den Stack Trace ausgibt
            log.error("Catalog HTTP call failed, uri={} durationMs={}", safeUri(uri), durationMs, e);

            throw e; // oder in eigene Exception verpacken
        }
    }

    private String safeUri(URI uri) {
        // Query-Parameter aus Logs entfernen: dort koennen Benutzereingaben sein
        return uri.getScheme() + "://" + uri.getHost() + uri.getPath();
    }
}

Bei diesem Muster ist das Muster der Logs wichtig; im Arbeitscode wird die Ausnahme normalerweise spezifischer gemacht als nur Exception.

6. Wo Fehler loggen: ein Stack Trace

Im ReadLater Starter haben wir bereits eine Schichtung: Entrypoint in app, Services in catalog.service, externe Integration in catalog.client, plus allgemeines common. Und hier kann man leicht einen häufigen Fehler machen: eine Ausnahme abfangen und sie in jeder Schicht „für alle Fälle“ loggen.

Das Ergebnis sieht so aus: Ein echter Fehler verwandelt sich in fünf ERROR-Zeilen, jede mit demselben Stack Trace. Im Log entsteht das Gefühl, dass „überhaupt alles kaputt ist“, obwohl es tatsächlich nur ein konkretes Problem ist – z.B. ein Timeout.

Eine gute Strategie: Fehler dort loggen, wo der maximale nützliche Kontext vorhanden ist und wo es wirklich eine „Grenze“ des Systems ist. Für einen externen HTTP-Aufruf ist diese Grenze oft catalog.client, weil dort URI, Status, Dauer, Modus real/mock und Nuancen des Transportfehlers bekannt sind. Höher, in catalog.service, ist es oft nicht nötig, den Stack Trace zu wiederholen – es reicht, die Ausnahme entweder weiterzureichen oder in eine benutzerdefinierte Meldung umzuwandeln.

Es ist praktisch, so zu denken: catalog.client ist für die technische Wahrheit verantwortlich („Der Dienst hat nicht geantwortet“, „hat 503 zurückgegeben“, „hat lange geantwortet“), während catalog.service und app für das Anwendungsverhalten verantwortlich sind („wir zeigen dem Benutzer eine kurze Meldung“, „im Mock-Modus nehmen wir sample JSON“). Logs sind auf beiden Ebenen nötig, aber sie unterscheiden sich im Sinne.

Wenn man unbedingt ein „Signal nach oben“ haben möchte, kann man es so machen: Auf client / boundary loggen wir error mit exception, und auf service oder app wiederholen wir diese Stelle entweder gar nicht oder lassen einen kurzen warn/info ohne Stack Trace stehen. Dann haben Sie keinen „Chor“, aber Kontext.

7. Sichere Grenzen von Logs: was nicht schreiben

Logs sind kein persönliches Tagebuch des Entwicklers, das Sie vor dem Schlafengehen löschen. In echten Projekten wandern sie oft in ein zentrales Speichersystem, leben monatelang, werden indiziert, von anderen Menschen gelesen und werden manchmal zur Quelle von Datenlecks. Daher die einfache und sogar etwas paranoide Regel: Wenn Sie sich nicht sicher sind, ob etwas geloggt werden darf, loggen Sie es besser nicht.

Auch für unser Lernprojekt ist dies wichtig, weil sich die richtigen Gewohnheiten nicht dann bilden, wenn „die Sicherheit bereits brennt“, sondern wenn Sie einfach normalen Code schreiben. Und ja, dies ist der seltene Fall, in dem eine „Gewohnheit“ Sie tatsächlich vor sehr unangenehmen Situationen in der Zukunft bewahren kann.

Beispiel schlechter Logs: Token und „rohe“ Payloads

Sehen wir uns zwei Meldungen an, die fast garantiert zu Ihrem Problem werden:

log.info("Authorization token={}", token);  // schlechte Idee: Geheimnis in Logs
log.info("Raw response body={}", body);     // schlechte Idee: riesiger und potenziell sensibler Payload
// Kommentar: Solche Logs tauchen normalerweise nicht sofort auf, sondern spaeter – wenn die Logs bereits im zentralen Speicher sind

Im ersten Fall haben Sie buchstäblich ein Geheimnis in die Log-Datei/das Log-System geschrieben. Im zweiten Fall riskieren Sie, das Log in Megabyte an Text zu ertränken (und gleichzeitig „versehentlich“ das aufzuzeichnen, was nicht aufgezeichnet werden sollte: E-Mail, Benutzername, einige Providerdaten, was auch immer). Auch wenn es jetzt so scheint, dass „es ja nur Bücher sind“ – die Gewohnheit, den Body vollständig zu loggen, wird auf andere Projekte übertragen, wo sie dann eine echte Schwachstelle darstellt.

Womit ersetzen: Zusammenfassung statt „alles“

In den meisten Fällen ist für die Diagnose nicht der Antwortkörper nötig, sondern eine Zusammenfassung: Status, Dauer, Antwortgröße. Manchmal – die ersten N Zeichen, aber streng auf debug und nur, wenn Sie sicher sind, dass keine sensiblen Daten enthalten sind.

// Statt des Koerpers – eine kurze Zusammenfassung: sie ist fast immer nuetzlicher und sicherer
log.info("Catalog response: status={} bodyLength={}", statusCode, body.length());

Diese Meldung liefert oft erstaunlich viel: Sie sehen, dass eine Antwort kam, dass sie nicht leer ist und dass sie „ungefähr wie die Wahrheit aussieht“. Und wenn Sie tiefer gehen müssen, schalten Sie DEBUG für das entsprechende Paket ein und fügen eine eingeschränkte Vorschau hinzu.

„Sichere URI“: wann man query besser weglässt

URI enthält oft Query-Parameter, und Query-Parameter enthalten oft Benutzereingaben. Benutzereingaben sind potenziell personenbezogene Informationen. Daher die einfachste sichere Strategie: Schema, Host und Path loggen, aber Query nicht loggen (oder nur die Parameternamen loggen, ohne Werte).

private String safeUri(URI uri) {
    // Query-String nicht loggen: dort koennen personenbezogene Daten oder einfach 'sehr viel Text' sein
    return uri.getScheme() + "://" + uri.getHost() + uri.getPath();
}

Ja, dies ist keine „ideale universelle Sanitisierung“. Aber für das Niveau unseres Kurses ist es ein ausgezeichnetes Minimum, das nicht behindert zu verstehen, wohin Sie gegangen sind, und nicht alles in die Logs kippt, was der Benutzer in die Suche eingegeben hat.

8. Platzhalter und teure Berechnungen in Logs

SLF4J-Platzhalter {} sind nicht nur für die „Schönheit“ nützlich, sondern auch, um unnötige String-Arbeit zu vermeiden, wenn der Log-Level ausgeschaltet ist. Wenn Sie log.debug(...) schreiben und Ihr root level=INFO ist, wird die debug-Nachricht nicht ausgegeben – und es wäre gut, wenn Sie keine Zeit mit der Vorbereitung einer riesigen Zeichenkette verschwenden, die ohnehin nicht benötigt wird.

Aber es gibt eine versteckte Falle: Platzhalter helfen nicht, wenn Sie „teure“ Werte vor dem Logger-Aufruf berechnen. Z.B. body.substring(0, Math.min(body.length(), 200)) oder mapper.writeValueAsString(object) werden trotzdem ausgeführt, selbst wenn debug ausgeschaltet ist. Daher werden solche Dinge entweder gar nicht gemacht oder unter if (log.isDebugEnabled()) ausgeführt.

// Wichtig: 'teure' Berechnungen nur durchfuehren, wenn debug tatsaechlich aktiviert ist
if (log.isDebugEnabled()) {
    String preview = body.substring(0, Math.min(body.length(), 200));
    log.debug("Catalog response preview: {}", preview);
}

Dies ist kein Aufruf, überall isDebugEnabled() zu schreiben (man kann verrückt werden und die Lesbarkeit verlieren), aber es ist ein nützliches Werkzeug für seltene Fälle, in denen Sie wirklich ein Stück des Payloads für die Diagnose benötigen. Im Normalmodus ist es besser, mit Zusammenfassungen zu leben: Status, Dauer, Größe.

9. Typische Fehler beim Loggen

Beim Loggen externer Aufrufe und Ausnahmen gibt es ein paar Rechen, auf die fast alle treten, und das ist normal: Sie hatten nur noch nicht das Vergnügen, einmal „einen halben Tag mit der Untersuchung ohne Logs zu verbringen“. Besser, man tritt auf diese Rechen im Lernprojekt – sanft und mit einem lehrreichen „Bumm“ – als am Arbeitsplatz am Freitagabend.

Fehler Nr. 1: Nur loggen, „dass etwas schiefgelaufen ist“, ohne Kontext.
Eine Meldung wie log.error("Request failed") wirkt wie ein Schrei ins Leere. Nach einer Stunde erinnern Sie sich nicht mehr, welcher spezifische Aufruf, welcher Modus, welche URI, welche query. Fügen Sie immer ein Minimum an Kontext hinzu: Vorgang, sichere URI, Dauer, Status (falls vorhanden).

Fehler Nr. 2: Ausnahme ohne Stack Trace loggen (nur e.getMessage()).
Wenn Sie log.error("Failed: {}", e.getMessage()) schreiben, werfen Sie die Hälfte der nützlichen Informationen weg. Der Stack Trace ist eine Karte, auf der mit einem Kreuz die Stelle des Fehlers markiert ist. Die richtige Variante: log.error("...", e) und plus einige Kontextparameter über {}.

Fehler Nr. 3: Denselben error auf jeder Schicht duplizieren.
Wenn ein externer Aufruf fehlschlägt und Sie den Stack Trace in catalog.client, dann noch einmal in catalog.service und noch einmal in app loggen, erhalten Sie das Gefühl „alles stürzt ein“ und ertrinken in identischen Zeilen. Loggen Sie detailliert dort, wo der beste Kontext ist, und darüber – entweder wiederholen Sie das Loggen nicht oder halten Sie ein Geschäftsereignis fest, ohne den Stack Trace zu wiederholen.

Fehler Nr. 4: Token, Passwörter und „interessante Header“ loggen, nur weil es „bequemer zum Debuggen ist“.
Diese Bequemlichkeit endet dort, wo das Leck beginnt. Alle Geheimnisse sollten standardmäßig als tabu für Logs gelten. Wenn Sie denken, „ohne Token ist unklar“ – dann brauchen Sie eine andere Diagnose, kein Geheimnis im Log.

Fehler Nr. 5: Standardmäßig den vollständigen Response-Body loggen.
Selbst wenn der Payload harmlos erscheint, kann er riesig sein und manchmal unerwartet Dinge enthalten, die nicht gespeichert werden sollten. Standardmäßig loggen Sie eine Zusammenfassung: Status, Dauer, Größe. Vorschau ist nur punktuell auf debug erlaubt und besser unter isDebugEnabled().

1
Aufgabe
Java Server, Level 21, Lektion 3
Gesperrt
Sicherer Log einer externen Anfrage
Sicherer Log einer externen Anfrage
1
Aufgabe
Java Server, Level 21, Lektion 3
Gesperrt
Protokollierung einer Ausnahme mit Kontext und Dauer
Protokollierung einer Ausnahme mit Kontext und Dauer
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION