Idea en 30 segundos
Distributed tracing muestra un run no dentro de un solo servicio, sino en toda la cadena de llamadas.
En sistemas multiagente, una solicitud suele pasar por gateway, runtime, tools, colas y proveedores LLM.
Distributed tracing conecta esos pasos con trace_id, span_id y parent_span_id, por eso el comportamiento del sistema se ve end-to-end.
Problema principal
Cuando un agente opera en varios servicios, los logs suelen quedar dispersos.
Se ven por separado errores de gateway, de tool service y de agent runtime.
Pero esos eventos no quedan ligados como un solo run.
Sin trace context compartido, cuesta entender que es el mismo run.
Como resultado, incluso un incidente simple se convierte en investigación larga:
- no está claro en qué servicio apareció la latencia;
- no se entiende dónde se perdió el contexto;
- cuesta conectar retries entre servicios;
- es difícil reconstruir el camino completo de un run problemático.
Por eso los sistemas multiagente necesitan distributed tracing, no solo tracing local dentro de un runtime.
Cómo funciona
Distributed tracing usa el mismo modelo de trace y span, pero entre varios servicios.
trace— todo el camino de una solicitud por todos los serviciosspan— una operación concreta en un servicio
En sistemas reales, estos campos suelen basarse en OpenTelemetry (OTel):
trace_id— identificador compartido del camino completospan_id— identificador del paso actualparent_span_id— vínculo con el paso padreservice_name— dónde se ejecutó el pasooperation_name— qué hizo el servicio
Para que el trace no se corte, el trace context debe propagarse entre servicios en cada llamada.
Normalmente se hace por headers (traceparent) o metadata en mensajes de cola.
Cómo se ve un distributed trace
La forma más simple de entender distributed tracing es un ejemplo de una solicitud.
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
Ese trace muestra:
- el camino completo entre servicios;
- qué servicio aportó la mayor latencia;
- dónde exactamente se rompió el contexto (si pasó);
- qué spans fueron padres y cuáles hijos.
Cuándo usar
Distributed tracing no siempre es necesario.
Si el sistema es monolítico y todo el run vive en un solo proceso, muchas veces alcanza tracing local.
Pero distributed tracing se vuelve crítico cuando:
- una solicitud pasa por varios servicios;
- en el workflow hay colas o workers async;
- varios agentes intercambian eventos;
- se necesita análisis preciso de latency y retries entre servicios.
Ejemplo de implementación
Abajo hay un ejemplo simplificado de cómo propagar trace context entre gateway y un servicio worker.
En el ejemplo se usan headers simplificados (x-trace-id, x-parent-span-id) para mostrar el principio de propagación.
En producción normalmente se usa el header estándar W3C traceparent (vía OpenTelemetry), que propaga automáticamente trace context entre servicios.
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) # ejemplo de llamada HTTP/gRPC a worker_handle_request en otro servicio
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:
# ... trabajo del agente, tool calls, pasos LLM ...
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)
Incluso el enfoque manual de arriba ayuda a entender la mecánica base de distributed tracing.
En un workflow real, cada servicio suele crear su propio span y después pasar su span_id como parent_span_id al siguiente hop.
Si se omite ese paso, el siguiente servicio iniciará un trace nuevo y se rompe la visión end-to-end.
Por ejemplo, un span-event en log JSON puede verse así:
{
"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"
}
Errores típicos
Incluso con distributed tracing ya implementado, en production siguen apareciendo problemas típicos.
Nuevo trace_id en cada servicio
Si cada servicio genera su propio trace_id, el trace end-to-end se rompe en fragmentos.
En ese modo, cuesta localizar la causa raíz del incidente entre servicios.
Se propaga solo trace_id, sin relación de spans
trace_id sin span_id ni parent_span_id da solo una lista plana de eventos.
Sin árbol de spans, cuesta entender qué pasos estaban anidados.
El contexto se pierde en colas async
Si la metadata de la cola no incluye trace context, parte del workflow queda fuera del trace.
Esos cortes suelen ocultar una fase temprana de caída parcial o de fallos en cascada.
Falta service_name y operation_name
Sin esos campos se ve que hay un error, pero no queda claro en qué servicio y operación ocurrió. Eso vuelve el debugging mucho más lento.
Autoevaluación
Abajo tienes un checklist corto de distributed tracing base antes del release.
Progreso: 0/9
⚠ Falta observability base
Será difícil depurar el sistema en production. Empieza con run_id, structured logs y tracing de tool calls.
FAQ
Q: ¿En qué se diferencia distributed tracing del agent tracing normal?
A: Agent tracing muestra pasos dentro de un runtime. Distributed tracing conecta pasos entre varios servicios en un solo trace end-to-end.
Q: ¿Qué se rompe si entre servicios se propaga solo trace_id, sin span_id ni parent_span_id?
A: El trace queda plano: se ve que los eventos pertenecen al mismo camino, pero cuesta entender qué pasos estaban anidados, qué servicio llamó a cuál y dónde exactamente apareció la latencia o el error. trace_id une el camino global, mientras span_id y parent_span_id construyen el árbol de pasos.
Q: ¿Cómo propagar trace context por colas?
A: Agrega trace_id, span_id y parent_span_id en metadata del mensaje. Si no, los pasos async quedan fuera del trace.
Q: ¿Es obligatorio implementar OpenTelemetry desde el primer día?
A: No. Puedes comenzar con propagación manual y logs estructurados, y luego pasar a OTel SDK cuando el sistema crezca.
Páginas relacionadas
Sigue con estos temas:
- Observabilidad para agentes de IA — visión base de traces, logs y metrics.
- Tracing del agente — tracing de un run dentro de un servicio.
- Debugging de runs de agentes — cómo analizar runs problemáticos paso a paso.
- Failure alerting — cómo detectar temprano cortes y degradación.
- Métricas de agentes — qué métricas se necesitan para monitoreo en production.