Es ist drei Uhr morgens. Die Bereitschaftscrew ist wach, und die Logs scrollen in unleserlicher Geschwindigkeit über den Bildschirm. Zehntausend identische Einträge mit dem Text „Fehler: Anfrage fehlgeschlagen – Zeitüberschreitung“ füllen die letzten 15 Minuten. Die Zeitstempel sind eng gepackt, die Pfade der Anfragen unklar, und die Ursachenkette bleibt unsichtbar. Irgendwo im System scheitert ein nachgelagerter Aufruf an einen Inventardienst. Doch welche Anfrage löste die Kaskade aus? Welcher Nutzer ist betroffen? Die Logs geben keine Antwort – nicht in einer einzigen Spalte.
Doch dieses Problem ist kein Qualitätsmangel der Logs. Sie sind korrekt formatiert, präzise getimt und zentral aggregiert. Das Team folgt den Empfehlungen aus dem Jahr 2014. Der wahre Grund liegt in der Struktur: Eine Logzeile ist die falsche Analyseeinheit für solche Ausfälle. Erst verteilte Traces eröffnen die richtige Perspektive.
Die Grenzen von Logzeilen in verteilten Systemen
Eine strukturierte Logzeile beantwortet eine einfache Frage: „Was beobachtete dieser Dienst zu diesem Zeitpunkt?“ Sie ist lokal, momentbezogen und – per Definition – ohne Bezug zu einem übergeordneten Anfragelifecycle. In Monolithen mag das ausreichen: Die gesamte Anfrage läuft in einem Prozess, und ein gemeinsamer request_id reicht aus, um die Zusammenhänge nachträglich zu rekonstruieren.
Doch in einer Microservice-Architektur bricht diese Annahme zusammen. Eine Nutzeranfrage, die über einen Kubernetes-Eingang eintrifft, durchläuft typischerweise fünf bis zwanzig interne Dienste: einen Auth-Gateway, einen Session-Resolver, mehrere Domain-APIs, eine Feature-Flag-Schicht, Datenbanken und möglicherweise einen Empfehlungs- oder Abrechnungsdienst. Jeder dieser Dienste generiert eigene Logs – oft in separaten Systemen und ohne gemeinsame Korrelationsfelder. Die Anfrage existiert, doch ihre Struktur bleibt unsichtbar.
Das Problem lässt sich nicht innerhalb der Log-Abstraktion lösen. Eine Logzeile kann per Definition keine Informationen über den Aufrufgraphen enthalten, da dieser zum Zeitpunkt der Protokollierung unbekannt ist. Die Lösung liegt in einem anderen Signal: dem verteilten Trace.
Traces als ganzheitliche Antwort auf Systemfehler
Ein Trace beantwortet die Frage: „Was passierte bei einer einzelnen Anfrage über alle Dienste hinweg?“ Er besteht aus einer Hierarchie von Spans, wobei jeder Span eine Arbeitseinheit darstellt – einen API-Aufruf, eine Datenbankabfrage oder einen HTTP-Request. Die Eltern-Kind-Beziehungen der Spans spiegeln die kausale Abfolge wider. Ein eindeutiger trace_id wandert vom Eingang der Anfrage durch alle Zwischenschritte; jeder Span trägt zusätzlich die ID des übergeordneten Spans sowie Metadaten wie HTTP-Methode, Fehlercode oder Nutzerkennung.
Visualisiert erscheint ein Trace als Wasserfalldiagramm: Die horizontale Achse zeigt die Zeit, die vertikale die beteiligten Dienste. Breite Balken markieren langsame Spans, rote Balken Fehler. Die zentrale Frage – „Welcher der zwanzig Dienste verursachte die Verzögerung oder den Fehler?“ – lässt sich nun auf einen Blick beantworten, statt mit zwanzig grep-Befehlen zu suchen.
Traces sind keine verbesserten Logs. Sie sind eine eigenständige Datenklasse mit einem anderen Analysefokus (die gesamte Anfrage statt eines Moments) und einem anderen Speichermodell (ein Baum pro Anfrage statt ein Strom pro Dienst). Beide ergänzen sich, ersetzen sich aber nicht.
Standards, die die Branche verändern
Verteilte Traces sind kein neues Konzept. Bereits 2010 veröffentlichte Google mit dem Dapper-Papier von Sigelman und Kollegen die theoretische Grundlage. Twitter folgte 2012 mit der Open-Source-Implementierung Zipkin, und Uber stellte 2017 Jaeger vor – allesamt inspiriert von Dapper. Doch lange dominierten herstellerspezifische Lösungen: Jeder APM-Anbieter (Datadog, New Relic, AppDynamics, Dynatrace) bot eigene SDKs an, was zu einer unfreiwilligen Bindung an einen Anbieter führte.
Dies änderte sich durch zwei Meilensteine:
- Trace Context (W3C, 6. Februar 2020): Der Standard definiert ein herstellerneutrales Übertragungsformat für Trace-IDs über HTTP-Grenzen hinweg. Ein
traceparent-Header enthält die Trace-ID, die ID des übergeordneten Spans und Sampling-Flags, während der optionaletracestate-Header herstellerspezifische Kontexte transportiert. Die meisten modernen HTTP-Clients und Frameworks unterstützen diesen Standard mittlerweile.
- OpenTelemetry (CNCF Incubating seit August 2021): Das Projekt entstand aus der Fusion von OpenTracing und OpenCensus und bietet SDKs für alle gängigen Sprachen (Node.js, Python, Java, Go, .NET, Rust, Ruby, PHP). Der OTLP-Protokollstandard und ein Collector-Tool ermöglichen die flexible Weiterleitung der Telemetriedaten an beliebige Backends. Besonders praktisch: Automatische Instrumentierung von HTTP-Servern, ORMs oder Message-Queues – ohne manuellen Code.
Die Konsequenz: Statt sich an einen APM-Anbieter zu binden, können Teams nun mit OpenTelemetry instrumentieren und die Daten an das Backend ihrer Wahl senden – sei es Jaeger, Grafana Tempo oder ein selbst gehostetes System. Die Instrumentierung wird damit nicht nur einfacher, sondern auch portabler.
Fazit: Traces als unverzichtbares Werkzeug für moderne Systeme
Logs bleiben wichtig – sie liefern detaillierte Einblicke in einzelne Ereignisse. Doch in verteilten Architekturen sind sie allein nicht ausreichend. Verteilte Traces bieten die notwendige Ganzheitlichkeit, um Fehlerketten zu verstehen, Performance-Probleme zu lokalisieren und die Ursachen von Ausfällen zu identifizieren. Mit Standards wie Trace Context und OpenTelemetry steht eine robuste, herstellerneutrale Grundlage zur Verfügung.
Die Frage ist nicht mehr, ob man Traces einsetzt – sondern wie schnell man sie in die eigene Infrastruktur integriert. Wer bis 3 Uhr morgens noch mit Logs kämpft, sollte über den Wechsel nachdenken.
KI-Zusammenfassung
Discover why traditional logs fall short in distributed systems and how distributed tracing provides the missing context to debug failures and latency in microservices.