Idee in 30 Sekunden
Distributed Tracing zeigt einen Run nicht nur innerhalb eines einzelnen Services, sondern über die gesamte Aufrufkette.
In Multi-Agenten-Systemen läuft eine Anfrage oft durch Gateway, Runtime, Tools, Queues und LLM-Provider.
Distributed Tracing verknüpft diese Schritte über trace_id, span_id und parent_span_id, sodass das Verhalten end-to-end sichtbar wird.
Hauptproblem
Wenn ein Agent über mehrere Services läuft, sind Logs meistens verteilt.
Gateway-Fehler sieht man separat, Tool-Service-Fehler separat, Agent-Runtime separat.
Aber diese Events sind nicht als ein Run verbunden.
Ohne gemeinsamen trace context ist schwer zu erkennen, dass es derselbe Run ist.
Dadurch wird selbst ein einfacher Incident zu einer langen Untersuchung:
- unklar, in welchem Service die Verzögerung entstand;
- unklar, wo der Kontext verloren ging;
- schwer, Retries serviceübergreifend zu verbinden;
- schwer, den vollständigen Pfad eines problematischen Runs zu rekonstruieren.
Deshalb brauchen Multi-Agenten-Systeme Distributed Tracing und nicht nur lokales Tracing innerhalb einer Runtime.
Wie es funktioniert
Distributed Tracing nutzt dasselbe Modell aus trace und span, aber über mehrere Services hinweg.
trace— der gesamte Pfad einer Anfrage über alle Servicesspan— eine konkrete Operation in einem Service
In realen Systemen basieren diese Felder meist auf OpenTelemetry (OTel):
trace_id— gemeinsame ID für den gesamten Pfadspan_id— ID des aktuellen Schrittsparent_span_id— Verbindung zum Eltern-Schrittservice_name— wo der Schritt ausgeführt wurdeoperation_name— was der Service gemacht hat
Damit der Trace nicht abreißt, muss trace context bei jedem Aufruf zwischen Services weitergegeben werden.
Üblicherweise geschieht das über Headers (traceparent) oder Metadata in Queue-Nachrichten.
Wie ein Distributed Trace aussieht
Distributed Tracing versteht man am einfachsten über ein Anfragebeispiel.
trace_id: tr_7a31
user_query: "Find vendor invoices for March"
gateway span g1 parent=- 18ms status=ok
agent_runtime span a1 parent=g1 240ms status=ok
tool_service span t1 parent=a1 410ms status=ok
agent_runtime span a2 parent=a1 130ms status=ok
llm_provider span l1 parent=a2 690ms status=ok
stop_reason: completed
Ein solcher Trace zeigt:
- den vollständigen Pfad zwischen Services;
- welcher Service die größte Latenz verursacht;
- wo genau der Kontext abgerissen ist (falls passiert);
- welche Spans Eltern und welche Kinder sind.
Wann einsetzen
Distributed Tracing ist nicht immer erforderlich.
Wenn das System monolithisch ist und der gesamte Run in einem Prozess lebt, reicht lokales Tracing oft aus.
Distributed Tracing wird aber kritisch, wenn:
- eine Anfrage mehrere Services durchläuft;
- der Workflow Queues oder Async-Worker enthält;
- mehrere Agenten Events austauschen;
- präzise Analyse von Latenz und Retries zwischen Services nötig ist.
Implementierungsbeispiel
Unten ist ein vereinfachtes Beispiel, wie trace context zwischen Gateway und Worker-Service weitergegeben wird.
Im Beispiel werden vereinfachte Header (x-trace-id, x-parent-span-id) genutzt, um das Prinzip zu zeigen.
In Production werden meist standardisierte W3C-Header traceparent (über OpenTelemetry) verwendet, die den Trace Context automatisch zwischen Services weitergeben.
import contextvars
import logging
import time
import uuid
logger = logging.getLogger("distributed-tracing")
trace_id_ctx = contextvars.ContextVar("trace_id", default=None)
span_id_ctx = contextvars.ContextVar("span_id", default=None)
def start_span(service_name, operation_name, parent_span_id=None):
span_id = str(uuid.uuid4())
started_at = time.time()
logger.info(
"span_started",
extra={
"trace_id": trace_id_ctx.get(),
"span_id": span_id,
"parent_span_id": parent_span_id,
"service_name": service_name,
"operation_name": operation_name,
},
)
return span_id, started_at
def finish_span(service_name, operation_name, span_id, started_at, status, parent_span_id=None, error=None):
logger.info(
"span_finished",
extra={
"trace_id": trace_id_ctx.get(),
"span_id": span_id,
"parent_span_id": parent_span_id,
"service_name": service_name,
"operation_name": operation_name,
"status": status,
"latency_ms": int((time.time() - started_at) * 1000),
"error": error,
},
)
def inject_context(headers):
headers["x-trace-id"] = trace_id_ctx.get() or ""
headers["x-parent-span-id"] = span_id_ctx.get() or ""
def extract_context(headers):
incoming_trace_id = headers.get("x-trace-id") or str(uuid.uuid4())
incoming_parent_span_id = headers.get("x-parent-span-id")
trace_token = trace_id_ctx.set(incoming_trace_id)
return incoming_parent_span_id, trace_token
def gateway_handle_request():
trace_id = str(uuid.uuid4())
trace_token = trace_id_ctx.set(trace_id)
root_span_id, root_started_at = start_span("gateway", "handle_request", parent_span_id=None)
span_token = span_id_ctx.set(root_span_id)
try:
headers = {}
inject_context(headers)
call_worker_service(headers) # beispielhafter HTTP/gRPC-Aufruf zu worker_handle_request in einem anderen Service
finish_span(
"gateway",
"handle_request",
root_span_id,
root_started_at,
status="ok",
parent_span_id=None,
)
except Exception as error:
finish_span(
"gateway",
"handle_request",
root_span_id,
root_started_at,
status="error",
parent_span_id=None,
error=str(error),
)
raise
finally:
span_id_ctx.reset(span_token)
trace_id_ctx.reset(trace_token)
def worker_handle_request(headers):
parent_span_id, trace_token = extract_context(headers)
span_id, started_at = start_span("worker", "process_task", parent_span_id=parent_span_id)
span_token = span_id_ctx.set(span_id)
try:
# ... Agent-Arbeit, Tool-Calls, LLM-Schritte ...
finish_span("worker", "process_task", span_id, started_at, status="ok", parent_span_id=parent_span_id)
except Exception as error:
finish_span(
"worker",
"process_task",
span_id,
started_at,
status="error",
parent_span_id=parent_span_id,
error=str(error),
)
raise
finally:
span_id_ctx.reset(span_token)
trace_id_ctx.reset(trace_token)
Selbst der manuelle Ansatz oben hilft, die Grundmechanik von Distributed Tracing zu verstehen.
Im realen Workflow erstellt jeder Service meist seinen eigenen Span und gibt dessen span_id als parent_span_id für den nächsten Hop weiter.
Wenn dieser Schritt fehlt, startet der nächste Service einen neuen Trace und das end-to-end Bild bricht.
Zum Beispiel kann ein Span-Event im JSON-Log so aussehen:
{
"timestamp": "2026-03-21T15:17:00Z",
"event": "span_finished",
"trace_id": "tr_7a31",
"span_id": "sp_worker_02",
"parent_span_id": "sp_gateway_01",
"service_name": "worker",
"operation_name": "process_task",
"latency_ms": 410,
"status": "ok"
}
Typische Fehler
Auch wenn Distributed Tracing bereits vorhanden ist, bleiben in Production oft typische Probleme.
Neue trace_id in jedem Service
Wenn jeder Service eine eigene trace_id erzeugt, zerfällt der end-to-end Trace in Fragmente.
In diesem Modus ist es schwer, die Ursache eines serviceübergreifenden Incidents zu lokalisieren.
Nur trace_id, ohne Span-Beziehungen
trace_id ohne span_id und parent_span_id ergibt nur eine flache Eventliste.
Ohne Span-Baum ist schwer zu verstehen, welche Schritte verschachtelt waren.
Kontextverlust in Async-Queues
Wenn Queue-Metadata keinen trace context enthält, fallen Teile des Workflows aus dem Trace.
Solche Lücken maskieren oft eine frühe Phase von teilweisem Ausfall oder kaskadierenden Ausfällen.
Fehlendes service_name und operation_name
Ohne diese Felder sieht man zwar einen Fehler, aber nicht in welchem Service und welcher Operation. Dadurch dauert Debugging deutlich länger.
Selbstcheck
Unten ist eine kurze Checkliste für grundlegendes Distributed Tracing vor dem Release.
Fortschritt: 0/9
⚠ Grundlegende Observability fehlt
Das System wird in production schwer zu debuggen sein. Starten Sie mit run_id, structured logs und tracing von tool calls.
FAQ
Q: Worin unterscheidet sich Distributed Tracing von normalem Agent Tracing?
A: Agent Tracing zeigt Schritte innerhalb einer Runtime. Distributed Tracing verbindet Schritte über mehrere Services zu einem end-to-end Trace.
Q: Was geht kaputt, wenn zwischen Services nur trace_id, aber nicht span_id und parent_span_id propagiert wird?
A: Dann wird der Trace flach: Man sieht, dass Events zu einem Pfad gehören, aber es ist schwer zu erkennen, welche Schritte verschachtelt waren, welcher Service wen aufgerufen hat und wo genau Verzögerung oder Fehler entstanden. trace_id verbindet den Gesamtpfad, span_id und parent_span_id bauen den Schrittbaum.
Q: Wie überträgt man Trace Context über Queues?
A: trace_id, span_id und parent_span_id in Message-Metadata aufnehmen. Sonst fallen Async-Schritte aus dem Trace.
Q: Muss man OpenTelemetry sofort einführen?
A: Nein. Man kann mit manueller Propagation und strukturierten Logs starten und später auf OTel SDK wechseln, wenn das System wächst.
Verwandte Seiten
Weiter zum Thema:
- Observability für KI-Agenten — Basissicht auf traces, logs und metrics.
- Agent Tracing — Tracing eines Runs innerhalb eines Services.
- Debugging von Agent-Runs — problematische Runs Schritt für Schritt analysieren.
- Failure Alerting — wie man Abbrüche und Degradation früh erkennt.
- Agent-Metriken — welche Metriken für Production-Monitoring nötig sind.