Observability
Logs, Metriken und Traces für einen realen Fehlerpfad verbinden.
Workshop-Slice
Dieses Lab zeigt einen konkreten Enterprise-Fehler mit Vorher/Nachher-Code. Die Beispiele sind bewusst fachlich benannt, damit du Architekturentscheidungen und nicht nur Syntax übst.
Lernziel
Du machst einen Fehlerfall so beobachtbar, dass Support und Betrieb ihn ohne Debugger nachvollziehen können.
Ausgangslage
Ein sporadischer Billing-Fehler tritt nur unter Last auf. Logs enthalten Freitext ohne Order-ID, Trace-ID oder Metriken.
Vorher: problematischer Code
Freitext-Logs helfen lokal, aber nicht im zentralen Betrieb.
try {
legacy.createInvoice(request);
log.info("invoice ok");
} catch (Exception e) {
log.error("failed");
throw e;
}
Analyse: Was ist daran schlecht?
- Keine Order-ID im Log.
- Kein strukturierter Fehlercode.
- Kein Timer für Legacy-Latenz.
- Trace-ID fehlt im Fehlervertrag.
Nachher: bessere Lösung
Die Zielversion verbindet strukturierte Logs, Metriken und Trace-Kontext.
class BillingAdapter {
private final ObservationRegistry observationRegistry;
private final MeterRegistry meterRegistry;
InvoiceId createInvoice(ConfirmedOrder order) {
return Observation.createNotStarted("billing.soap.create_invoice", observationRegistry)
.lowCardinalityKeyValue("system", "legacy-billing")
.highCardinalityKeyValue("order.id", order.orderId().value())
.observe(() -> callLegacy(order));
}
private InvoiceId callLegacy(ConfirmedOrder order) {
long started = System.nanoTime();
try {
InvoiceId invoiceId = soap.createInvoice(order);
meterRegistry.counter("billing.invoice.created").increment();
log.info("invoice_created orderId={} invoiceId={}", order.orderId().value(), invoiceId.value());
return invoiceId;
} catch (LegacyBillingUnavailable ex) {
meterRegistry.counter("billing.invoice.failed", "reason", "legacy_unavailable").increment();
log.warn("invoice_failed orderId={} errorCode={} message={}",
order.orderId().value(), "BILLING_LEGACY_UNAVAILABLE", ex.getMessage());
throw ex;
} finally {
meterRegistry.timer("billing.soap.duration").record(System.nanoTime() - started, TimeUnit.NANOSECONDS);
}
}
}
### Fehlercode BILLING_LEGACY_UNAVAILABLE
1. Trace-ID aus ProblemDetails kopieren.
2. In Logs nach `orderId` und `errorCode=BILLING_LEGACY_UNAVAILABLE` suchen.
3. Dashboard `billing.soap.duration` prüfen.
4. Falls `billing.invoice.failed{reason="legacy_unavailable"}` steigt: Legacy-Billing-Eskalation starten.
Test / Prüfnachweis
Dieser Abschnitt zeigt, wie du die Verbesserung nachweist. Es ist bewusst kein reiner Happy-Path-Test, sondern prüft ein Risiko aus dem Vorher-Teil.
@Test
void failedLegacyCallIncrementsFailureMetric() {
adapter.createInvoice(orderThatFails());
assertThat(meterRegistry.counter("billing.invoice.failed", "reason", "legacy_unavailable").count())
.isEqualTo(1.0);
}
Typische Fehler
- Logmeldungen ohne fachliche ID.
- Hohe Kardinalität in Metriklabels.
- Trace-ID nicht bis API-Fehler durchreichen.
- Runbook erst nach Incident schreiben.
Deep-Learning-Bezug
Die Links führen zum ausführlichen Inhalt; die Deep-Learning-Seite bleibt nur die Lernlandkarte und kopiert den Inhalt nicht doppelt.
Prüfcheckliste
- Jeder Fehlerpfad hat orderId oder fachliche Korrelations-ID.
- Metriken haben kontrollierte Labels.
- Runbook beschreibt Suche und Eskalation.
- Trace-ID taucht in API und Logs auf.