Context Propagation ist der Mechanismus, der Trace-Kontext wie Trace-ID, Parent-Span-ID und Sampling-Entscheidung mit jedem Request oder jeder Nachricht von einem Service zum nächsten trägt. Erst dadurch werden Spans aus verschiedenen Prozessen zu einem einzigen Distributed Trace verknüpft. Das Standardformat für HTTP ist der traceparent-Header aus W3C Trace Context.
Was Context Propagation ist
Ein Distributed Trace setzt sich aus vielen Spans zusammen, die in unterschiedlichen Prozessen entstehen, oft auf verschiedenen Maschinen und in verschiedenen Sprachen. Jeder Service kennt nur seine eigene Arbeit. Context Propagation teilt dem nachgelagerten Service mit: „Dieser Request gehört zu Trace X, und der Span, der dich aufruft, ist Y.“ Der Service erfasst seine Spans mit derselben Trace-ID und Y als Parent, sodass das Backend den vollständigen Request-Baum rekonstruieren kann.
Der Kontext besteht aus zwei Teilen. Der Span Context identifiziert Trace und aktuellen Span und trägt Flags, etwa ob der Trace gesampelt wird. Baggage sind optionale, selbst definierte Schlüssel-Wert-Paare, zum Beispiel eine Mandanten-ID, die mit dem Request mitreisen, damit spätere Services sie nutzen können.
Funktionsweise: Inject und Extract
Propagation erfolgt in zwei Schritten. Bei einem ausgehenden Aufruf injiziert die Instrumentierung den aktuellen Kontext in den Träger, typischerweise HTTP-Header, gRPC-Metadaten oder Message-Header. Der empfangende Service extrahiert den Kontext und macht ihn zum Parent des neuen Server-Spans. In OpenTelemetry übernehmen sogenannte Propagatoren diese Aufgabe, die Umgebungsvariable OTEL_PROPAGATORS wählt sie aus. Standard ist tracecontext,baggage.
Auch innerhalb eines Prozesses muss Kontext über Threads, Callbacks und asynchrone Tasks fließen. Laufzeitumgebungen lösen das unterschiedlich, etwa über Thread-Local Storage in Java oder Async Context Tracking in Node.js. Die Context-API von OpenTelemetry abstrahiert diese Unterschiede.
Der Standard W3C Trace Context
Vor der Standardisierung nutzte jedes Tracing-System eigene Header, etwa die B3-Header von Zipkin oder herstellerspezifische Formate. Traces rissen dadurch an jeder Grenze zwischen Tools ab. Die W3C-Recommendation Trace Context definiert zwei HTTP-Header, die alle konformen Systeme verstehen:
traceparent: 00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01
tracestate: vendorname=opaque-value
| Feld | Beispiel | Bedeutung |
|---|---|---|
| version | 00 | Formatversion, derzeit 00 |
| trace-id | 0af7651916cd43dd8448eb211c80319c | 16 Byte als 32 Hex-Zeichen, identifiziert den gesamten Trace |
| parent-id | b7ad6b7169203331 | 8 Byte als 16 Hex-Zeichen, ID des aufrufenden Spans |
| trace-flags | 01 | Bitfeld; 01 bedeutet, dass der Aufrufer den Trace gesampelt hat |
Der Header tracestate transportiert zusätzlich herstellerspezifische Daten, die separate W3C-Spezifikation Baggage definiert den Header baggage für eigene Schlüssel-Wert-Paare.
Messaging und asynchrone Verarbeitung
Auch Queues und Event-Streams brauchen Propagation. Bei Kafka schreibt etwa die Instrumentierung des Producers traceparent in die Record-Header, der Consumer liest ihn aus. Weil ein Consumer-Batch Nachrichten aus vielen Traces enthalten kann, verbinden die OpenTelemetry-Konventionen für Messaging Consumer-Spans häufig über Span Links statt über einen einzelnen Parent mit ihren Producern. Ohne Propagation in den Message-Headern beginnt jeder asynchrone Schritt einen neuen, unverbundenen Trace.
Die Sampling-Entscheidung weitergeben
Das Sampled-Flag in traceparent ist für Kosten und Vollständigkeit entscheidend. Beim Head-based Sampling entscheidet der erste Service, ob ein Trace aufgezeichnet wird, und nachgelagerte Services übernehmen diese Entscheidung über einen Parent-based Sampler. Ignorieren Services das Flag und sampeln unabhängig voneinander, entstehen Trace-Fragmente statt vollständiger Traces. Tail-based Sampling im Collector umgeht das Problem, weil es erst entscheidet, wenn alle Spans eingetroffen sind.
Best Practices
- Einheitlich auf W3C Trace Context setzen, in allen Services, Proxys und Gateways. Zusätzliche Propagatoren wie B3 nur dort konfigurieren, wo Altsysteme sie benötigen.
- Header-Weitergabe durch Proxys sicherstellen. Load Balancer, API-Gateways und Service Meshes müssen
traceparentundtracestateunverändert durchreichen oder selbst am Tracing teilnehmen. - Baggage klein und unkritisch halten. Baggage wird mit jedem nachgelagerten Request versendet und kann Ihre Vertrauensgrenze verlassen. Zugangsdaten oder personenbezogene Daten gehören nicht hinein.
- Umgang mit externem Input festlegen. Wer
traceparentaus dem öffentlichen Internet übernimmt, lässt Clients das Sampling beeinflussen. Viele Teams starten am Edge einen neuen Trace und behalten den eingehenden Kontext als Link. - Trace-ID loggen.
trace_idundspan_idin strukturierten Logs verbinden Logs und Traces.
Häufige Fehler
Abgerissene Traces sind das typische Symptom einer Propagationslücke: ein Thread-Pool, der den Kontext verliert, ein selbst gebauter HTTP-Client ohne Instrumentierung, ein Message-Bus ohne Header-Unterstützung oder ein Proxy, der unbekannte Header entfernt. Eine weitere Quelle sind gemischte Propagator-Konfigurationen, bei denen ein Service nur B3 schreibt und der nächste nur W3C liest. Und wer in jedem Service eine neue Trace-ID erzeugt, etwa fürs Logging, hebelt das Prinzip vollständig aus.
Wie Obseria unterstützt
Obseria ist OpenTelemetry-nativ und nutzt W3C Trace Context durchgängig. Traces aus OpenTelemetry-SDKs und -Agents verbinden sich daher ohne proprietäre Header. Das Distributed Tracing in Obseria hebt Traces mit fehlenden Parents hervor und zeigt so, wo die Propagation abreißt. Mehr dazu unter Was ist OpenTelemetry? und in der Kafka-Integration.