Lección 3: Logs estructurados y métricas: convertir cada paso en datos
Objetivos de aprendizaje:
- Explicar las cuatro preguntas que la observabilidad en producción debe responder, y reconocer que son los mismos números que las métricas de evaluación, solo que usados de otra forma
- Diseñar logs estructurados para tu arnés: un registro por solicitud al modelo, uno por llamada a herramienta, con campos que cubran duración, tokens, nombre de herramienta y errores
- Mapear patrones de métricas a arreglos concretos mediante lecturas diagnósticas, y detectar cuándo señales como «cero errores» están deformadas por la manera en que se registran
Requisitos: Completar las Lecciones 1 y 2, y tener un bucle de arnés funcionando (Curso 7 de esta serie) | Anterior: << Lección 2 | Siguiente: Lección 4 >>
Una ejecución que puedes leer, doscientas que no
Al final de la Lección 2 hiciste algo que vale la pena: leíste una transcripción cruda de principio a fin y atrapaste tres problemas que el agente nunca mencionó. El método funciona y la evidencia es sólida. El problema es que eso fue una ejecución.
Ahora pon el mismo agente en producción: doscientas ejecuciones por día, cada una con una docena de iteraciones del bucle, sumando dos o tres mil idas y vueltas de llamadas a herramientas. El lunes por la mañana alguien dice «el lote del viernes por la tarde se sintió especialmente lento», ¿y cómo respondes? Leer doscientas transcripciones obviamente no es realista. Y aunque lo hicieras, seguirías sin poder responder dónde estuvo lento — ese juicio requiere mirar la distribución entre ejecuciones, no leer una muestra. Los ojos humanos pueden responder «por qué hizo esto en esta ejecución en particular», pero no «en qué se diferenció este lote del lote anterior».
Así que el trabajo de esta lección cabe en una frase: convertir preguntas que requieren leer transcripciones en preguntas que una sola consulta puede responder. Las primeras son «por qué buscó la misma palabra repetidamente esta vez»; las segundas son «qué herramienta se llamó más en los últimos siete días, y si los errores están todos en el mismo parámetro». La regla de la Lección 2 sigue en pie — las transcripciones crudas son evidencia de primera mano, el autorreporte del agente no cuenta. Esta lección solo guarda la misma evidencia en otro formato, para que pueda leerse por personas Y además filtrarse, agregarse y analizarse por distribución.
Cuatro preguntas que los entornos de producción deben responder
La documentación oficial lista cuatro cosas que necesitas ver con claridad en la observabilidad de producción: qué herramientas se llamaron, cuánto tardó cada solicitud al modelo, cuántos tokens se gastaron y dónde ocurrieron los fallos1. Al tomar decisiones de diseño volverás a estas cuatro preguntas una y otra vez: ¿este campo ayuda a responder alguna de ellas? Si no, es ruido.
Esto quizá te suene conocido. El Curso 10 de esta serie usó el mismo conjunto de números al hablar de evaluación: más allá de la exactitud del estado final, recomendaba recolectar el tiempo de ejecución de llamadas a herramientas individuales y de tareas completas, el conteo total de llamadas a herramientas, el consumo total de tokens y los errores de herramienta2. Mismas métricas, dos apariciones, usos distintos:
La diferencia no está en los números — está en contra qué los comparas. En evaluación comparas «antes del cambio frente a después del cambio» sobre un conjunto de pruebas fijo, así que los números tienen que ser reproducibles. En monitoreo comparas «hoy frente a los últimos siete días» o «esta sesión frente a otras sesiones», así que la referencia es el historial de las propias ejecuciones, lo que significa que los números tienen que ser continuos, marcados con hora y segmentables por dimensión. Esta lección cubre lo segundo.
Logs estructurados: un registro por paso
Esta sección describe una práctica de ingeniería. No hay guía autorizada sobre cómo nombrar campos de log ni a qué formato escribir en disco — lo que sigue es un punto de partida por defecto que funciona, no una especificación oficial. Los nombres de campo toman prestados términos que sí aparecen en materiales oficiales (session id, prompt id, nombre de herramienta, tool_input, tool_response, duration_ms, conteos de tokens, error), así que cuando más adelante te integres con telemetría oficial no tendrás que remapear el vocabulario.
Unidad de registro: uno para solicitud al modelo, uno para llamada a herramienta
El bucle del agente tiene naturalmente dos tipos de «paso»: una solicitud al modelo y una ejecución de herramienta. Tienen atributos muy distintos —las solicitudes al modelo tienen conteos de tokens pero no nombre de herramienta; las ejecuciones de herramienta son al revés— pero comparten el mismo lote de campos de contexto (qué sesión, qué prompt, cuánto tardó).
Entonces: escribe un registro por solicitud al modelo y uno por llamada a herramienta, y usa un campo type para distinguirlos. No comprimas una iteración entera del bucle en un solo registro — así nunca podrás calcular el reparto entre tiempo de modelo y tiempo de herramienta. Tampoco escribas un único registro resumen cuando la tarea termine — si la tarea se estanca a mitad de camino, ni siquiera sabrás en qué paso se estancó.
Formato: JSON Lines, un objeto por línea
JSON Lines (comúnmente escrito JSONL) es exactamente lo que suena: un archivo, cada línea es un objeto JSON completo, sin comas entre líneas y sin arreglo envolvente.
Las razones para elegirlo son todas mundanas pero todas válidas: las escrituras solo-append funcionan sin necesidad de volver atrás a agregar un ] al final del archivo, así que aunque maten el proceso no terminas con un archivo sintácticamente roto (la última línea podría quedar a medio escribir, pero todas las anteriores siguen siendo parseables — este es también el origen real del requisito del ejercicio de que «las líneas malas se reportan pero no detienen el procesamiento»). Cuando el archivo crece a cientos de megabytes, puedes procesarlo por streaming línea por línea. Cada línea es autocontenida, así que grep puede filtrar, jq puede procesar y los ojos humanos pueden leer.
Compara esto con los logs en prosa que muchos arneses ya tienen — [09:12:05] search_docs devolvió 3 resultados, tardó 412ms. Se lee bien, pero solo lo pueden leer humanos. Para responder «cuál es la duración promedio de search_docs en los últimos siete días» tendrías que escribir una expresión regular que extraiga ese 412ms; si alguien cambia «tardó» por «demoró», la regex empieza a devolver cero en silencio. Los logs en prosa codifican la estructura dentro del lenguaje natural, y el lenguaje natural es para que lo decodifiquen humanos. Los logs estructurados lo invierten: la estructura vive en campos, y las máquinas la leen sin ambigüedad. Se pueden filtrar, agregar y analizar por distribución — esas tres capacidades son las que de verdad necesitas al pasar de una ejecución a doscientas.
Vocabulario de campos
Contexto que va en cada registro: ts (marca de tiempo ISO 8601 con milisegundos y zona horaria), type (model_call o tool_call), session_id (identificador de una sesión, se mantiene constante entre múltiples turnos), prompt_id (identificador de un prompt del usuario; todas las solicitudes al modelo y llamadas a herramientas que dispara comparten este valor), duration_ms. Las solicitudes al modelo agregan model, stop_reason, input_tokens / output_tokens. Las llamadas a herramientas agregan tool, tool_use_id (para emparejar con las respuestas) y error (presente solo cuando falla).
prompt_id es el campo menos llamativo de aquí, pero después se vuelve el más útil. Ahora mismo solo lo estás escribiendo en cada registro; la Lección 4 lo usará para cercar los registros dispersos en «eventos del mismo prompt» y después hilvanarlos en un árbol mediante relaciones padre-hijo. En cuanto a la entrada y el valor de retorno de la herramienta en sí (tool_input / tool_response) — no escribas el texto completo por defecto; registra solo largo o conteo de bytes. La justificación viene en la penúltima sección.
Integrarlo en el arnés
El fragmento de abajo se apoya en el bucle gobernado por stop_reason del Curso 7 de esta serie. Primero, el logger:
logRecord() hace una sola cosa: fusionar el contexto común con los campos que le pasa quien lo llama en una línea de JSON y agregarla al final del archivo. No juzga, no formatea, no hace nada «inteligente» — mientras más tonto el logger, mejor, porque cuando se rompe te quedas sin logs que revisar. Después, los dos puntos de instrumentación del bucle:
Dos detalles que vale la pena señalar.
Dónde arrancas y dónde detienes el cronómetro determina qué significa este número. t1 arranca antes de runTool y se detiene después de que retorna, así que duration_ms incluye los reintentos propios de la herramienta, las esperas de backoff y las idas y vueltas de red, pero no la validación de parámetros previa a la llamada. Tú defines esta frontera; una vez definida, escríbela — dentro de seis meses, cuando estés mirando un duration_ms de 30 segundos, vas a necesitar saber si incluye reintentos.
Los errores van tanto al log como al contexto del modelo. El bloque catch mete el mensaje de error de vuelta en tool_result, así que el agente lo ve en el siguiente turno. La guía oficial encaja perfecto aquí: cuando una llamada a herramienta lanza un error, habría que aplicarle ingeniería de prompts a la respuesta para que comunique con claridad mejoras específicas y accionables, no un código de error opaco ni una traza de pila2. Puedes escribir ETIMEDOUT en el log, pero lo que vuelve al modelo debería ser «La solicitud expiró (30 segundos). Este endpoint tiende a expirar en consultas de rango amplio; intenta reducir date_range a 7 días o menos».
Leer métricas de forma diagnóstica
El valor de una métrica no está en «hoy hicimos 1,283 llamadas a herramientas» como número — está en que ciertos patrones apuntan a ciertos arreglos. Las correlaciones oficiales son todas pistas que vale la pena verificar primero:
Muchas llamadas redundantes → quizá haya que ajustar los parámetros de paginación o de límite de tokens. Muchas llamadas redundantes a herramientas podrían sugerir que se justifica un ajuste de tamaño de los parámetros de paginación o de límite de tokens2. El modelo necesita encontrar un pasaje en la documentación, tu search_docs devuelve solo 5 resultados por página, así que tiene que hojear 28 páginas. Cada una de esas 28 llamadas es legal, cada una tiene éxito, las métricas no muestran ningún «error», pero todas son desperdicio. Sube los resultados por página a 25 y este patrón desaparece.
Muchos errores de parámetro inválido → probablemente a la descripción de la herramienta le falta claridad o ejemplos. Muchos errores de herramienta por parámetros inválidos podrían sugerir que a las herramientas les vendrían bien descripciones más claras o mejores ejemplos2. Esta es potente cuando los errores se agrupan en el mismo parámetro: siete errores diciendo todos invalid parameter: date_range significa que deberías revisar primero si tu descripción explica qué formato espera ese parámetro. La dirección de investigación es la descripción de la herramienta, no el modelo.
Rastrear las llamadas a herramientas revela otras cosas. Rastrear las llamadas a herramientas puede ayudar a revelar flujos de trabajo comunes que los agentes siguen y ofrecer oportunidades para consolidar herramientas2. Por ejemplo, si el 90% de las llamadas a read_file van seguidas de parse_config, quizá deberías ofrecer un read_config de un solo paso. Este tipo de descubrimiento nunca emerge de una sola ejecución — solo de agregados. Otro conjunto de lecturas útiles: analiza tus métricas de llamadas a herramientas para identificar las herramientas más usadas, las tasas de éxito por herramienta, los tiempos promedio de ejecución y los patrones de error por tipo de herramienta3.
Algunos problemas son inherentemente problemas de magnitud — no puedes decir «dónde está el mucho» sin mirar agregados. Anthropic documentó problemas tempranos así: rastrear la web sin parar buscando fuentes inexistentes4 — mirar cualquier búsqueda individual no va a marcar ningún error; necesitas alinear decenas de llamadas para ver que «está girando en el vacío».
Una experiencia general (sin fuente autorizada): la duración promedio casi siempre miente. 99 llamadas de 80ms más 1 llamada de 30 segundos promedian 379ms, que se ve algo lento pero aceptable; la realidad son 99 llamadas rápidas más una completamente estancada. Al leer duración, mira como mínimo la mediana y los percentiles altos, o ve directo a los pocos registros más lentos.
La trampa de leer números: qué está contando realmente tu señal
La forma más fácil de que una métrica mienta no es contando mal — es cuando lo que cuenta no es lo que tú crees que cuenta.
Mira un diseño de producto real. Claude Code reintenta internamente las solicitudes de API fallidas y emite un único evento api_error solo después de rendirse — ese evento es la señal terminal de esa solicitud; los intentos de reintento intermedios no se registran como eventos separados3. Este diseño tiene sentido: si cada reintento registrara un error, el gráfico de errores quedaría inundado de hipos transitorios que la autorrecuperación resolvió, ocultando cuántas solicitudes fallaron de verdad. El costo es que tienes que recordar esta semántica — «3 eventos api_error hoy» significa «3 solicitudes fallaron en última instancia», no «3 hipos de red», y no dice nada sobre cuántos reintentos exitosos se esconden debajo.
La misma página de documentación ofrece una lectura muy práctica: para distinguir si una sesión se recuperó de un error o se estancó por completo, agrupa los eventos por session id y revisa si existe un evento de solicitud de API posterior al error3. Si hay uno posterior, siguió adelante; si no, ahí se detuvo. Este juicio requiere un agrupamiento más una revisión de «si hay registros después del error», con una relación valor/esfuerzo altísima — el ejercicio de Nivel 2 te hace escribir exactamente esto. (El JSONL escrito por append está naturalmente ordenado en el tiempo, así que no necesitas ordenar explícitamente dentro de un solo archivo; cuando los logs vienen de múltiples procesos, ordena primero por ts.)
De esta trampa puedes extraer una práctica general: escribe una frase por cada métrica que diga «esto cuenta qué». Escríbela en comentarios de código o en la documentación de campos. «Conteo de errores de herramienta = un conteo después de que fallan todos los reintentos» y «= un conteo por cada excepción lanzada» son dos métricas completamente distintas, pero el nombre puede ser idéntico, y quien lea el tablero dentro de seis meses no podrá distinguirlas solo por el número.
Costo y tokens: el número que más vale la pena vigilar
Si solo pudieras vigilar un número, vigila los tokens.
Primero, la magnitud. En los datos de Anthropic, los agentes suelen usar unas 4× más tokens que las interacciones de chat, y los sistemas multiagente usan unas 15× más tokens que los chats4. Esta es su observación sobre sus propios sistemas, no una constante universal, pero fija una expectativa: cuando conviertas una funcionalidad de chat en un agente, la factura no va a subir «un poquito». Tienen otra observación estadística: el uso de tokens por sí solo explica el 80% de la varianza, con el número de llamadas a herramientas y la elección de modelo como los otros dos factores explicativos4 — esto viene de su párrafo que analiza el desempeño de la evaluación, o sea «qué cantidades explican mejor las diferencias entre ejecuciones», y los tokens quedan primeros. Léelas juntas: los tokens son a la vez la parte más grande de la factura y el principal factor explicativo de la varianza entre ejecuciones, así que entre las métricas candidatas es la que más vale la pena vigilar primero.
Dos notas prácticas. Los números de costo son aproximaciones: la documentación oficial dice que las métricas de costo son aproximaciones; para datos de facturación oficiales, consulta a tu proveedor de API3. Así que su uso es «detectar anomalías, comparar tendencias», no «cuadrar con finanzas». La atribución necesita segmentarse por dimensión: las métricas de uso se pueden usar para rastrear tendencias entre equipos o individuos, identificar sesiones de alto uso y además atribuir el gasto a cosas específicas como nombre de skill, nombre de plugin o tipo de subagente3. La implicación para arneses caseros es directa — escribe esas dimensiones en los registros de log desde el principio, no intentes unirlas después; unir dimensiones a posteriori es básicamente volver a ejecutar. Además, copia los conteos de tokens directamente del campo usage de la respuesta del modelo; no los estimes con conteo de caracteres dividido entre 4 ni métodos parecidos — esos se desvían notablemente en escenarios de idiomas mezclados, con mucho código o con imágenes incluidas.
Contención: no inventes umbrales, no registres contenido completo
Una vez que tienes métricas, el siguiente impulso natural es poner alertas: tasa de error por encima del 5%, alerta; duración del percentil alto por encima de 10 segundos, alerta.
Alto. Esta lección no da números de umbral, porque no los hay en materiales autorizados. La documentación oficial menciona que alguien debería encargarse de las alertas, pero nunca ha dado valores específicos — presupuestos de error, objetivos de SLO, umbrales de alerta, ni un solo número. Si yo escribiera aquí «se recomienda 5%», eso me lo estaría inventando, y tú lo usarías. Los umbrales solo pueden crecer desde tu propia línea base: registra dos semanas de datos primero, mira el rango de fluctuación normal, y después define qué cuenta como anormal. Invierte el orden y obtienes una regla que da falsas alarmas tres veces al día y que todo el mundo silencia a las dos semanas.
La división de responsabilidades también vale la pena copiarla de los productos oficiales: Claude Code emite únicamente el flujo de eventos crudo; la detección de anomalías, el cálculo de líneas base, la correlación entre sesiones y las alertas son responsabilidad de tu SIEM o de tu backend de observabilidad3. Para arneses caseros esto significa: el sistema observado no emite juicios por sí mismo. No escribas «después de 3 errores de herramienta seguidos, manda un correo» dentro del arnés — esa lógica se despliega con el agente, se reinicia con el agente y se rompe con el agente, y no tiene datos históricos contra los cuales comparar.
Una última cosa, y también la más fácil de convertirse en incidente tres meses después del lanzamiento: no registres contenido por defecto. Claude Code no recolecta el contenido de los prompts del usuario por defecto — solo el largo del prompt; para incluir contenido tienes que definir explícitamente una variable de entorno3. La telemetría del Agent SDK es igualmente estructural primero — cada span registra duración, nombre de modelo y nombre de herramienta; los conteos de tokens se registran cuando la API devuelve datos de uso, pero el contenido que tu agente lee y escribe no se registra por defecto1.
Estos dos valores por defecto reflejan el mismo juicio: la información estructural (quién, cuándo, cuánto tardó, qué herramienta, cuántos tokens) alcanza para responder la enorme mayoría de las preguntas de operación; el contenido no. Una vez que el contenido entra a los logs, sigue a los logs hacia los respaldos, hacia el almacenamiento de largo plazo, hacia la vista de cualquiera con permisos de lectura. Así que tu arnés debería registrar por defecto input_bytes: 137 en lugar de tool_input: {...}; cuando de verdad necesites diagnosticar los parámetros exactos de una llamada específica, activa el registro completo para ese caso puntual. Esto no entra en conflicto con el «las transcripciones crudas son evidencia de primera mano» de la Lección 2: al depurar sí deberías ver la ida y vuelta completa, en un entorno que controlas, para una ejecución específica, y terminas cuando la leíste. Los logs de producción están por defecto en retención de largo plazo y visibilidad para varias personas — eso es otra cosa.
Fronteras: dónde se detiene esta lección
A esta altura tienes un montón de registros estructurados y un conjunto de métricas legibles. Tres cosas que esta lección no hace: hilvanar relaciones padre-hijo entre registros (qué solicitudes al modelo disparó un prompt, qué llamada a herramienta se anida bajo qué subagente) requiere ID de correlación para construir un árbol — eso es la Lección 4. Enganchar sondas en los puntos de control del ciclo de vida sin modificar el código del arnés son los hooks de la Lección 5. Montar toda esta capa sobre tu arnés del Curso 7 y recorrer un simulacro de depuración completo es la Lección 6.
💻 Ejercicios
Resumen
- La observabilidad en producción debe responder cuatro preguntas: qué herramientas se llamaron, cuánto tardó cada solicitud al modelo, cuántos tokens se gastaron y dónde ocurrieron los fallos1. Estas cuatro y las métricas de evaluación del Curso 10 de esta serie (tiempo de ejecución de llamadas individuales y de tareas completas, conteo total de llamadas a herramientas, consumo total de tokens, errores de herramienta)2 son el mismo conjunto de números — en evaluación los usas para juzgar si un cambio mejoró las cosas; en monitoreo los usas para vigilar la salud de las ejecuciones.
- El diseño de campos de log y la elección de JSONL no tienen especificación autorizada — es tu decisión de ingeniería. Punto de partida por defecto: un registro por solicitud al modelo, uno por llamada a herramienta, un objeto JSON por línea, con session id, prompt id, duración, conteos de tokens, nombre de herramienta y errores. Los logs en prosa solo los pueden leer humanos; los estructurados se pueden filtrar, agregar y analizar por distribución.
- El valor de las métricas está en que los patrones se mapean directamente a arreglos: muchas llamadas redundantes significan que hay que ajustar los parámetros de paginación o de límite de tokens; muchos errores de parámetro inválido significan que a las descripciones de las herramientas les falta claridad o ejemplos2. Rastrear las llamadas a herramientas también revela flujos de trabajo comunes de los agentes y oportunidades para consolidar herramientas2. Cuando una llamada a herramienta lanza un error, la respuesta en sí debería escribirse como una guía específica y accionable, no como un código de error opaco2.
- La semántica de una señal la define cómo se registra. Claude Code reintenta internamente las solicitudes de API fallidas y emite un único evento
api_error solo después de rendirse — es una señal terminal para esa solicitud; los reintentos intermedios no se registran por separado3 — así que un «conteo de errores» puede esconder debajo muchos reintentos invisibles. Para distinguir si una sesión se recuperó o se estancó, agrupa los eventos por session id y revisa si existe un evento de solicitud posterior al error3.
- Los tokens son la métrica que más vale la pena vigilar: en los datos de Anthropic, los agentes usan unas 4× los tokens del chat, y los sistemas multiagente unas 15×4. Al analizar el desempeño de la evaluación, encontraron que el uso de tokens por sí solo explica el 80% de la varianza, con el conteo de llamadas a herramientas y la elección de modelo como los otros dos factores explicativos4. Las métricas de costo son aproximaciones; la facturación oficial viene de tu proveedor de API3. El gasto se puede atribuir a cosas específicas como nombre de skill, nombre de plugin o tipo de subagente3.
- Dos principios de contención: el sistema observado emite únicamente el flujo de eventos crudo; la detección de anomalías, el cálculo de líneas base y las alertas son responsabilidad del backend3. Los logs no deberían registrar contenido por defecto — los productos oficiales por defecto no recolectan el contenido de los prompts, solo el largo3; la telemetría por defecto registra solo información estructural, no lo que el agente lee y escribe1. Los umbrales de alerta y los SLO no tienen números en materiales autorizados — no los inventes; registra primero una línea base de dos semanas.
>> Lección 4: Trazado: hilvanar una ejecución en un árbol