Idea en 30 segundos
El logging de agentes responde una pregunta simple: qué pasó exactamente durante un run.
Para eso se necesitan eventos estructurados con correlación mediante run_id y trace_id.
Sin eso, en incidentes normalmente se ve solo la respuesta final, pero no el camino que llevó a ella.
Problema principal
En un backend normal, pocos logs por solicitud suelen ser suficientes.
En sistemas de agentes, una solicitud puede incluir reasoning, tool calls, retries y varios pasos de modelo. Si solo se loggea el final, cuesta entender dónde exactamente se rompió el sistema.
En production normalmente se ve así:
- un usuario reporta una respuesta incorrecta;
- costos o latency suben en olas;
- en logs aparece un error aislado sin contexto de run.
Por eso los agentes necesitan logging estructurado de eventos en todo el ciclo de vida del run, no logs sueltos.
Cómo funciona
La idea base es simple: cada paso importante se registra como un evento estructurado separado.
Mínimo para cada evento:
run_idytrace_idpara correlación;event(qué pasó);timestamp;status(ok/error) donde aplique;- campos clave del paso (tool, latency, stop_reason, etc.).
Qué eventos loggear primero
| Evento | Que conviene registrar |
|---|---|
| run_started | run_id, trace_id, request_id, user_id |
| agent_step | step_type, step_index, tool |
| tool_call | tool_name, args_hash |
| tool_result | tool_name, latency_ms, status, error_class |
| llm_result | model, token usage, latency_ms, status |
| run_finished | stop_reason, total_steps, total_latency_ms |
En sistemas de production, raw prompts y raw tool args normalmente no se guardan sin redaccion. Lo mas comun es guardar hash o version anonimizada.
Cuándo usar
El logging profundo no siempre es necesario.
Para un escenario simple single-shot, a veces alcanza con logs minimos request -> response.
Pero en cuanto hay tools, retries, varios pasos o costos altos, sin logging estructurado se vuelve dificil:
- debugear incidentes;
- explicar costos;
- ajustar alertas de forma estable.
Ejemplo de implementación
Abajo tienes un ejemplo simplificado de structured logging en runtime y tool gateway.
En el ejemplo no se guardan raw args: se guarda args_hash.
agent_step registra el paso del agente, mientras tool_call y tool_result detallan por separado inicio y resultado de la llamada de herramienta.
import hashlib
import json
import logging
import time
import uuid
logger = logging.getLogger("agent")
def stable_hash(value):
payload = json.dumps(
value,
sort_keys=True,
ensure_ascii=False,
default=str, # para datetime y tipos complejos; en sistemas criticos conviene formato estable (por ejemplo ISO 8601)
).encode("utf-8")
return hashlib.sha256(payload).hexdigest()
def log_event(event, **fields):
logger.info(event, extra={"event": event, **fields})
def run_agent(agent, task, user_id=None, request_id=None):
run_id = str(uuid.uuid4())
trace_id = str(uuid.uuid4())
started_at = time.time()
steps = 0
stop_reason = "max_steps"
run_status = "ok"
log_event(
"run_started",
run_id=run_id,
trace_id=trace_id,
user_id=user_id,
request_id=request_id,
task_hash=stable_hash(task),
)
try:
for step in agent.iter(task): # step: reasoning o tool execution
steps += 1
step_started_at = time.time()
step_type = step.type
tool_name = getattr(step, "tool_name", None)
log_event(
"agent_step",
run_id=run_id,
trace_id=trace_id,
step_index=steps,
step_type=step_type,
tool=tool_name,
)
if step_type == "tool_call":
args = getattr(step, "args", {})
log_event(
"tool_call",
run_id=run_id,
trace_id=trace_id,
tool=tool_name,
args_hash=stable_hash(args),
)
try:
result = step.execute()
latency_ms = int((time.time() - step_started_at) * 1000)
if step_type == "tool_call":
log_event(
"tool_result",
run_id=run_id,
trace_id=trace_id,
tool=tool_name,
latency_ms=latency_ms,
status="ok",
)
else:
token_usage = getattr(result, "token_usage", None)
log_event(
"llm_result",
run_id=run_id,
trace_id=trace_id,
step_type=step_type,
model=getattr(step, "model", None),
token_usage=token_usage,
latency_ms=latency_ms,
status="ok",
)
except Exception as error:
latency_ms = int((time.time() - step_started_at) * 1000)
result_event = "tool_result" if step_type == "tool_call" else "llm_result"
log_event(
result_event,
run_id=run_id,
trace_id=trace_id,
step_type=step_type,
tool=tool_name,
model=getattr(step, "model", None),
latency_ms=latency_ms,
status="error",
error_class=type(error).__name__,
error_message=str(error),
)
run_status = "error"
stop_reason = "tool_error" if step_type == "tool_call" else "step_error"
raise
if result.is_final:
stop_reason = "completed"
break
finally:
log_event(
"run_finished",
run_id=run_id,
trace_id=trace_id,
status=run_status,
stop_reason=stop_reason,
total_steps=steps,
total_latency_ms=int((time.time() - started_at) * 1000),
)
En production estos eventos suelen enviarse a un sistema centralizado de logs (por ejemplo ELK, Datadog o ClickHouse), y luego se usan para dashboards y alertas.
Este ejemplo alcanza para:
- encontrar un tool call problemático;
- calcular latency por paso;
- entender por qué se detuvo un run.
Por ejemplo, un registro JSON puede verse asi:
{
"timestamp": "2026-03-21T15:17:00Z",
"event": "tool_result",
"run_id": "run_9fd2",
"trace_id": "tr_9fd2",
"tool": "search_docs",
"latency_ms": 410,
"status": "ok"
}
Errores típicos
Incluso con logging agregado, los incidentes suelen ser dificiles de analizar por errores tipicos como estos.
Se loggea solo la respuesta final
Sin eventos intermedios no se ve como el agente llego al resultado. En ese modo, incluso un incidente simple tarda demasiado.
No hay identificadores estables (run_id, trace_id)
Cuando los eventos no se correlacionan, no se puede reconstruir el run completo. En production esto suele convertir el debugging en busqueda manual entre servicios.
Se loggean raw prompts o raw args sin redaccion
Eso es un riesgo directo de fuga de datos personales o sensibles. Es mas seguro guardar hash, campos redactados o version anonimizada.
No se loggean tool_result y stop_reason
Si faltan tool_result y stop_reason, cuesta entender que se rompio exactamente.
Esos huecos suelen ocultar fallo de herramienta o una fase temprana de spam de herramientas.
Autoevaluación
Abajo tienes un checklist corto de logging base de agentes 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 logging de tracing?
A: Logging responde "que paso" y registra eventos. Tracing muestra "como paso exactamente" mediante secuencia de pasos y relaciones.
Q: Qué conviene loggear primero si casi no hay logging?
A: Empieza por base: run_id, trace_id, run_started, tool_call, tool_result, run_finished, stop_reason. Eso ya da base de debugging.
Q: ¿Se pueden loggear prompts completos?
A: Por defecto, mejor no. En production los prompts suelen contener datos sensibles. Es mas seguro hash o version redactada.
Q: ¿Cómo saber si el logging ya alcanza?
A: Si puedes reconstruir la secuencia de un run problematico y ubicar el punto de fallo en 5-10 minutos, el nivel base ya funciona.
Páginas relacionadas
Sigue con estos temas:
- Observabilidad para agentes de IA — modelo general de traces, logs y metrics.
- Tracing del agente — cómo ver el camino de un run paso a paso.
- Tracing distribuido de agentes — como conectar eventos entre servicios.
- Debugging de runs de agentes — analisis practico de incidentes.
- Metricas de agentes — indicadores para operacion estable.