Logs — El Registro del Comportamiento
Logs — El Registro del Comportamiento
¿Qué son los logs?
Los logs son registros de eventos discretos que ocurrieron en un sistema en un momento específico en el tiempo. Son la forma más antigua y ubicua de instrumentación de software — prácticamente todo programa desde los años 60 ha emitido algún tipo de log.
Un log es, en su forma más simple, una línea de texto que describe algo que ocurrió:
2026-04-04 14:23:45 ERROR Failed to connect to database
Pero los logs modernos son mucho más ricos que esto, y la diferencia entre logs bien y mal diseñados puede significar la diferencia entre resolver un incidente en 5 minutos o en 5 horas.
El problema con los logs tradicionales: texto no estructurado
La mayoría de los sistemas legacy emiten logs como texto plano no estructurado. El problema es inmediato cuando intentas buscar en ellos:
Apr 4 14:23:45 prod-server-01 myapp[1234]: User johndoe logged in from 192.168.1.100
Apr 4 14:23:46 prod-server-01 myapp[1234]: Processing payment for order #98765, amount: $149.99
Apr 4 14:23:47 prod-server-01 myapp[1234]: ERROR: Payment gateway timeout after 3000ms
Apr 4 14:23:47 prod-server-01 myapp[1234]: Retrying payment for order #98765, attempt 2/3
Para buscar todos los pagos fallidos de un usuario específico en el último mes, necesitas:
- Acceder a los archivos de log de todos los servidores
- Parsear el formato con expresiones regulares frágiles
- Hacer joins manuales entre diferentes líneas
- Esperar minutos u horas si el volumen es grande
Esto no escala. En un sistema con 50 servidores emitiendo 10,000 líneas por segundo, el log unstructured es prácticamente innavegable.
Logs estructurados: la solución moderna
Los logs estructurados son logs emitidos en un formato que puede ser parseado automáticamente, típicamente JSON. Cada campo es explícito, tipado y consultable.
{
"timestamp": "2026-04-04T14:23:47.423Z",
"level": "error",
"service": "payment-service",
"version": "2.3.1",
"environment": "production",
"trace_id": "a1b2c3d4e5f6g7h8",
"span_id": "i9j0k1l2",
"user_id": "usr_johndoe_12345",
"order_id": "ord_98765",
"event": "payment_gateway_timeout",
"gateway": "stripe",
"timeout_ms": 3000,
"attempt": 2,
"max_attempts": 3,
"message": "Payment gateway timeout, retrying"
}
Ahora puedes hacer consultas como:
user_id = "usr_johndoe_12345" AND event = "payment_gateway_timeout"— todos los timeouts de este usuariogateway = "stripe" AND timeout_ms > 5000 AND @timestamp > now()-1h— timeouts graves de Stripe en la última horaservice = "payment-service" AND level = "error" AND attempt = 3— pagos que fallaron en el último intento
Esta es la diferencia entre logs como texto plano y logs como datos.
graph LR
subgraph "Logs No Estructurados"
UL[Texto plano\nnov 4 14:23 ERROR payment failed] --> UP[Parse con regex\nfrágil y lento]
UP --> US[Búsqueda limitada\ny manual]
end
subgraph "Logs Estructurados"
SL[JSON/Key-Value\ntimestamp, level, user_id, event] --> SP[Indexado automático\npor cada campo]
SP --> SS[Búsqueda rápida\npor cualquier campo]
end
Los niveles de severidad
Los niveles de log permiten filtrar el ruido y enfocarse en lo importante. El estándar más común sigue la convención de syslog/RFC 5424:
| Nivel | Uso | Ejemplo |
|---|---|---|
| TRACE | Información extremadamente detallada, solo para debugging intensivo | Entrada/salida de cada función |
| DEBUG | Información útil durante desarrollo y debugging | Estado de variables, flujo de control |
| INFO | Eventos normales del negocio | Usuario se registró, pedido completado |
| WARN | Situaciones anómalas que no son errores pero merecen atención | Retry exitoso, uso alto de recursos |
| ERROR | Errores que impactan una operación específica | Pago fallido, request timeout |
| FATAL/CRITICAL | Errores que impiden que el sistema funcione | No puede conectarse a la base de datos, crash |
Regla de oro para los niveles
flowchart TD
Q1{¿Detuvo el sistema?} -->|Sí| FATAL[FATAL/CRITICAL]
Q1 -->|No| Q2{¿Falló una operación\npara el usuario?}
Q2 -->|Sí| ERROR[ERROR]
Q2 -->|No| Q3{¿Algo inusual\npero recuperable?}
Q3 -->|Sí| WARN[WARN]
Q3 -->|No| Q4{¿Es un evento\nimportante del negocio?}
Q4 -->|Sí| INFO[INFO]
Q4 -->|No| Q5{¿Útil para\ndebugging?}
Q5 -->|Sí| DEBUG[DEBUG]
Q5 -->|No| TRACE[TRACE]
Errores comunes con los niveles
Over-logging en ERROR: El error más frecuente. Si cada request que devuelve 404 es un ERROR, tu tablero de errores se convierte en ruido. Un 404 es un INFO o ni siquiera eso — es el comportamiento esperado.
Under-logging en INFO: Muchos sistemas no logean eventos de negocio importantes. “Usuario completó checkout”, “Suscripción cancelada”, “Archivo procesado” son eventos que tienen valor para negocio y debugging.
DEBUG en producción: Dejar DEBUG activado en producción puede aumentar el volumen de logs 10-100x, saturar el storage y ocultar los eventos importantes.
¿Qué incluir en un log?
Un log bien diseñado responde las preguntas: ¿Qué? ¿Quién? ¿Cuándo? ¿Dónde? ¿Con qué resultado?
Campos obligatorios
{
"timestamp": "2026-04-04T14:23:47.423Z", // ISO 8601, siempre UTC
"level": "error", // Nivel de severidad
"service": "payment-service", // Nombre del servicio
"environment": "production", // prod / staging / dev
"message": "Descripción humano-legible" // Qué pasó
}
Campos de trazabilidad
{
"trace_id": "a1b2c3d4e5f6g7h8", // Para correlacionar con trazas
"span_id": "i9j0k1l2", // Span actual
"request_id": "req_xyz789" // ID único de la request
}
Campos de contexto de negocio
{
"user_id": "usr_12345",
"tenant_id": "acme-corp",
"order_id": "ord_98765",
"session_id": "sess_abc"
}
Campos de contexto técnico
{
"host": "prod-server-01",
"pod": "payment-service-7d9b-xkp4z",
"version": "2.3.1",
"duration_ms": 234
}
Logging de errores: la anatomía completa
Cuando logueas un error, la información mínima debe permitir a alguien que nunca vio el sistema entender qué falló, en qué contexto, y con qué consecuencia:
{
"timestamp": "2026-04-04T14:23:47.423Z",
"level": "error",
"service": "payment-service",
"event": "payment_failed",
"message": "Payment processing failed after 3 attempts",
"error": {
"type": "PaymentGatewayException",
"message": "Connection timeout to Stripe API",
"stack": "PaymentService.processPayment (payment.service.ts:234)...",
"code": "GATEWAY_TIMEOUT"
},
"context": {
"user_id": "usr_12345",
"order_id": "ord_98765",
"amount_cents": 14999,
"currency": "USD",
"gateway": "stripe",
"attempts": 3
},
"impact": {
"user_facing": true,
"order_lost": false,
"retry_scheduled": true,
"retry_at": "2026-04-04T14:28:47.423Z"
},
"trace_id": "a1b2c3d4e5f6g7h8"
}
Logs y privacidad: qué NO loguear
Los logs son a menudo el vector de más fugas de datos sensibles. Reglas fundamentales:
graph TD
D[Dato a loguear] --> Q{¿Es sensible?}
Q -->|Sí| NEVER[NUNCA loguear]
Q -->|No| LOG[Loguear normalmente]
NEVER --> S1[Contraseñas / tokens]
NEVER --> S2[Números de tarjeta]
NEVER --> S3[SSN / documentos de identidad]
NEVER --> S4[Datos médicos]
NEVER --> S5[Claves privadas / secrets]
NEVER --> S6[PII sin justificación legal]
En lugar de loguear datos sensibles, logea referencias:
- No:
"card_number": "4111111111111111" - Sí:
"card_last4": "1111", "card_brand": "visa"
No: "password": "mysecret"
Sí: No lo loguees bajo ninguna circunstancia.
Arquitectura de un sistema de logs
En producción, los logs fluyen desde la aplicación hasta un sistema de almacenamiento y búsqueda:
flowchart LR
subgraph Aplicación
A1[Service A] --> CL[Log Collector\ne.g. Fluentd/Logstash]
A2[Service B] --> CL
A3[Service C] --> CL
end
CL --> |Transporte seguro\nTLS| AGG[Aggregator]
AGG --> STORE[(Log Storage\nElasticsearch/Loki/S3)]
STORE --> SEARCH[Search & Analysis\nKibana/Grafana]
STORE --> ALERT[Alert System]
STORE --> ARCHIVE[Cold Storage\nS3/GCS para retención larga]
Consideraciones de volumen
Un servicio mediano puede generar entre 1MB y 1GB de logs por hora dependiendo del nivel de verbosidad. A escala, el costo del almacenamiento y procesamiento de logs es significativo. Estrategias:
Sampling: Para eventos de alta frecuencia y baja importancia, loguea solo una muestra. Si procesas 1M de pagos exitosos al día, quizás logear 1% de los exitosos y 100% de los fallidos es suficiente.
Log levels by environment: En producción, INFO. En staging, DEBUG. En desarrollo, TRACE.
Retención diferenciada: Logs de ERROR → 90 días. Logs de INFO → 30 días. Logs de DEBUG → 7 días.
Correlación: el superpoder de los logs estructurados
El mayor beneficio de los logs estructurados es la capacidad de correlacionar eventos a través de servicios. El campo trace_id es el hilo que conecta todo.
Cuando un usuario reporta “mi pago no funcionó a las 3PM”, puedes:
- Encontrar el
trace_idasociado a esa request - Buscar todos los logs de todos los servicios con ese
trace_id - Reconstruir exactamente qué pasó, en qué servicio, en qué orden
trace_id: a1b2c3d4e5f6g7h8
14:23:45.100 [api-gateway] INFO Request received: POST /checkout
14:23:45.150 [order-service] INFO Order created: ord_98765
14:23:45.200 [inventory-service] INFO Stock reserved for ord_98765
14:23:45.250 [payment-service] INFO Payment processing started
14:23:47.423 [payment-service] ERROR Payment gateway timeout (3000ms)
14:23:47.450 [payment-service] WARN Retry 1/3 scheduled
14:23:50.450 [payment-service] ERROR Payment gateway timeout (3000ms)
14:23:50.500 [order-service] INFO Order ord_98765 marked as pending
14:23:50.520 [notification-service] INFO Email queued: payment_pending
En segundos tienes toda la historia de lo que pasó, sin grep manual, sin SSH, sin reuniones.
Mejores prácticas
mindmap
root((Mejores Prácticas\nde Logging))
Estructura
JSON siempre
Campos consistentes
No interpolación de strings
Contexto
trace_id en cada log
user_id cuando aplica
service y version
Contenido
Mensajes descriptivos
No datos sensibles
Error completo con stack
Operación
Sampling en alta frecuencia
Niveles correctos
Retención por nivel
Anti-patrones comunes
Log and throw: No loguees un error Y lo relances hacia arriba — terminarás con el mismo error logueado múltiples veces en diferentes capas, dificultando el diagnóstico.
Logging en bucles: Si procesas 10,000 items, no loguees un INFO por item. Loguea el inicio, el fin y los errores.
Mensajes sin contexto: "Error processing request" no le dice nada a nadie. "Error processing payment for order ord_98765: timeout after 3000ms" sí.
Timestamps en timezone local: Siempre UTC, siempre ISO 8601. Los logs de múltiples servidores en diferentes zonas horarias con timestamps locales son una pesadilla.
Herramientas populares
Recopilación y transporte:
- Fluentd / Fluent Bit — ligero, extensible
- Logstash — parte del stack ELK
- Vector — alta performance, escrito en Rust
Almacenamiento y búsqueda:
- Elasticsearch + Kibana — el estándar histórico
- Grafana Loki — optimizado para logs, integrado con Prometheus
- ClickHouse — columnar, extremadamente rápido para analytics
SaaS:
- Datadog Logs, Splunk, Sumo Logic, Logtail