Módulo 10 · Observabilidad y depuración

Lección 45 — Logs estructurados

Identificadores de correlación para seguir una petición por todo el sistema.

Publicada
En esta lección
  1. Ejercicio 1 — El JSON formatter
  2. Ejercicio 2 — El trace_id end-to-end
  3. Ejercicio 3 — El catálogo de eventos
  4. Ejercicio 4 — La consulta del diagnóstico
  5. Ejercicio 5 — El audit log vs el log
  6. Entrega

El rastro de TicketFlow, consultable. Sin solutions.md hasta entregar.

Ejercicio 1 — El JSON formatter

  1. Configura el LOGGING del §3 (pythonjsonlogger o el tuyo propio) y verifica: una línea de log del servicio es JSON válido con ts/level/logger/event/trace_id/version. Pega la línea.
  2. El extra como hábito: reescribe 5 llamadas de log que usan f-string a logger.info(event, extra={...}). El test: grep -rn 'logger.*f"' services/ devuelve vacío.
  3. La TZ: simula un servidor en UTC con TIME_ZONE=Europe/Madrid: ¿qué timestamp graba el log y cuál muestra la consola del colector? Decide y documenta: ¿el log lleva UTC SIEMPRE (¿y la hora del evento para el usuario? — la 31: la hora de negocio es dominio, no log).

Ejercicio 2 — El trace_id end-to-end

  1. Implementa el middleware + ContextFilter (§2) y el test: dos requests concurrentes (ThreadPoolExecutor, la 34) llevan trace_ids DISTINTOS en sus logs (sin mezcla bajo threads).
  2. La propagación a la cola: añade trace_id a los kwargs de enviar_email_confirmacion.delay y el trace_id_var.set al inicio de la tarea. Test: endpoint → tarea eager → el log de la tarea lleva el MISMO tid.
  3. La propagación al outbox y a la respuesta: el evento del outbox (25) lleva trace_id en el payload; la respuesta HTTP (y el problem+json del 26) lo expone como X-Request-ID/trace_id. Test E2E del hilo: POST reserva → el tid de la respuesta aparece en el log del endpoint, en el outbox y en el email fake.

Ejercicio 3 — El catálogo de eventos

  1. Escribe el catálogo completo: tabla evento | nivel | logger | claves extra | quién lo emite. Cubre los ~12 eventos del curso (dominio del 25, saga del 32, jobs del 29/31, DLQ del 30, auth del 18-20).
  2. El nivel correcto: re-clasifica los que tienes mal (¿el 404 del request como ERROR? ¿el reintento del gateway como ERROR? — el §4 decide). El test del spam: corre la suite de integración y cuenta los WARNING/ERROR: ¿el ratio es legible o el ruido se come la señal?
  3. Lo que NO va: busca en tu código logger.*request.data|payload|body y django.db.backends en DEBUG: cada hallazgo con su riesgo PII (23) y su fix (¿el campo que SÍ va: el id del usuario, no el body?).

Ejercicio 4 — La consulta del diagnóstico

  1. Con el log JSON en fichero (o el stack local): simula el incidente del 47 — "el cliente RF-8A21 dice que pagó y no confirma": escribe la consulta (jq/grep) que reconstruye la historia con UN trace_id. ¿Cuántos comandos te costó?
  2. El historial por usuario: "¿qué hizo u-841 el 2027-09-28 entre 14:00 y 15:00?" — la consulta y su implicación RGPD (23): ¿quién puede ejecutar esa consulta y con qué autorización? Documenta la política de acceso al log.
  3. La retención: decide (o documenta el default del proveedor) los 30 días + frío 1 año del §5 con el costo estimado (el volumen: los logs del 36 × 50k reservas/mes ≈ ¿GB?).

Ejercicio 5 — El audit log vs el log

  1. Implementa la distinción: el audit log (23/56) como TABLA append-only (quién, qué, cuándo, IP, resultado) para: login, cambio de rol, descarga de export RGPD, cambio de precio. El log de diagnóstico NO registra esto (o sí: decide y justifica la doble vida).
  2. El test del audit: un cambio de rol genera fila audit Y log WARNING; el export genera fila audit + log INFO. ¿Qué consulta del auditor (56) satisface cada uno?
  3. El dashboard del log: escribe la consulta del "top 5 de errores de la última hora agrupados por event" (jq o el lenguaje del stack). La consulta que la 46 convertirá en alerta.

Entrega

Pega la línea JSON, la tabla del catálogo, la consulta del incidente simulado y la política del audit vs log. Después: Lección 46 — Métricas, trazas y alertas.