Logging Backend - Wide Events
Implementa Wide Events (tambien conocidos como Canonical Log Lines): un evento estructurado por request que contiene TODO el contexto relevante en un solo registro.
Concepto Core
Un Wide Event es un unico log JSON que captura el ciclo completo de un request. En lugar de multiples logs dispersos ("user logged in", "fetched data", "returned response"), emitir UN evento al final con toda la informacion agregada.
Beneficios:
- Debuggear con UN query en vez de correlacionar multiples logs
- Analisis con SQL simple sobre campos estructurados
- Sin perdida de contexto entre logs
- Tail sampling efectivo (solo guardar eventos interesantes)
Comparacion: Log tradicional vs Wide Event:
// Log tradicional - multiples lineas dispersas
[10:30:00] INFO: User 789 logged in
[10:30:01] INFO: Fetching orders for user 789
[10:30:02] DEBUG: Query executed in 45ms
[10:30:02] INFO: Found 3 orders
[10:30:03] ERROR: Failed to send email notification
// Wide Event - todo en un solo registro
{
"timestamp": "2024-01-15T10:30:03.000Z",
"user": { "id": "789" },
"action": "fetch_orders",
"result": { "count": 3 },
"infra": { "db_time_ms": 45 },
"error": { "type": "email_failed", "message": "SMTP timeout" }
}
Estructura de un Wide Event
{
"timestamp": "2024-01-15T10:30:00.000Z",
"level": "info",
"service": "api-gateway",
"trace_id": "abc123",
"request": { "method": "POST", "path": "/api/orders", "duration_ms": 245 },
"user": { "id": "user_789", "plan": "premium" },
"business": { "order_id": "ord_456", "total": 150.00, "items_count": 3 },
"infra": { "db_queries": 4, "db_time_ms": 120, "cache_hits": 2 },
"error": null
}
Ver references/wide-event-schema.md para esquema completo con todos los campos recomendados.
Workflow de Implementacion
Fase 1: Middleware Base
- Crear middleware que inicializa el wide event al inicio del request
- Adjuntar el evento al contexto (req.wideEvent, AsyncLocalStorage, etc.)
- Emitir el log al finalizar el request
Ver references/implementation.md para codigo del middleware.
Fase 2: Enriquecimiento
- En cada handler/service, agregar campos al wide event
- Usar helpers tipados:
enrichUser(),enrichBusiness(),enrichError() - Acumular metricas: queries, cache hits, llamadas externas
Fase 3: Tail Sampling
- Implementar funcion de sampling que decide que loggear
- Siempre loggear: errores, latencia alta, usuarios especificos
- Samplear porcentaje de requests exitosos normales
Ver references/implementation.md para funcion de tail sampling.
Fase 4: Colorizacion (Desarrollo)
- Configurar colores por nivel (ERROR=rojo, WARN=amarillo)
- Configurar colores por contexto (Request=azul, User=verde)
- Formatear output legible en terminal
Ver references/colorization.md para configuracion de colores.
Checklist Rapido
- [ ] Middleware que crea wide event por request
- [ ] Contexto accesible en toda la cadena (AsyncLocalStorage / req)
- [ ] Campos request: method, path, status, duration_ms
- [ ] Campos user: id, plan, roles (si aplica)
- [ ] Campos business: entidades del dominio
- [ ] Campos infra: db_queries, cache_hits, external_calls
- [ ] Campos error: code, message, stack (si hay error)
- [ ] Tail sampling configurado
- [ ] Formato JSON en prod, coloreado en dev
Anti-patrones
Ver references/anti-patterns.md para errores comunes:
- Confundir structured logging con wide events
- Depender solo de OpenTelemetry
- Loggear multiples veces por request
- No incluir contexto de negocio
Referencias
references/wide-event-schema.md- Esquema JSON completo con todos los camposreferences/implementation.md- Codigo de middleware y helpersreferences/anti-patterns.md- Que NO hacerreferences/colorization.md- Configuracion de colores para dev
Troubleshooting
El wide event no captura datos del usuario
Sintoma: user: null en todos los logs
Causa comun: El middleware de auth corre despues del wide event middleware.
Solucion:
// Orden incorrecto
app.use(wideEventMiddleware('my-service'));
app.use(authMiddleware); // user no disponible cuando se crea el evento
// Orden correcto
app.use(authMiddleware);
app.use(wideEventMiddleware('my-service')); // user ya esta en req
Los logs salen vacios en infra.db
Sintoma: db: { queries_count: 0, total_time_ms: 0 }
Causa comun: No se estan usando los helpers de tracking.
Solucion: Wrappear las queries:
const query = async (sql: string) => {
const start = Date.now();
const result = await db.query(sql);
trackDbQuery(Date.now() - start);
return result;
};
Logs demasiado grandes en produccion
Sintoma: Costos altos de almacenamiento/ingesta
Solucion: Implementar tail sampling agresivo:
const shouldLog = (event: WideEvent): boolean => {
if (event.level === 'error') return true;
if (event.request.duration_ms > 1000) return true;
return Math.random() < 0.01; // Solo 1% del resto
};
No puedo correlacionar logs entre servicios
Sintoma: Cada servicio tiene su propio trace_id
Causa: No se esta propagando el header.
Solucion: Ver references/implementation.md seccion "Propagacion Cross-Service".
Lecturas Recomendadas
- Stripe Engineering - Canonical Log Lines - El articulo original que popularizo el patron
- Observability Wide Events 101 - Guia practica de implementacion