El rastro de TicketFlow, consultable. Sin solutions.md hasta entregar.
Ejercicio 1 — El JSON formatter
- 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.
- El
extracomo hábito: reescribe 5 llamadas de log que usan f-string alogger.info(event, extra={...}). El test:grep -rn 'logger.*f"' services/devuelve vacío. - 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
- 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).
- La propagación a la cola: añade trace_id a los kwargs de
enviar_email_confirmacion.delayy eltrace_id_var.setal inicio de la tarea. Test: endpoint → tarea eager → el log de la tarea lleva el MISMO tid. - 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
- 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).
- 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?
- Lo que NO va: busca en tu código
logger.*request.data|payload|bodyydjango.db.backendsen 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
- 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ó?
- 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.
- 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
- 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).
- 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?
- 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.