🎯 Meta: ver qué hace tu sistema en producción. Cuando algo va mal a las 3 AM, la diferencia entre "no tengo idea de qué pasa" y "aquí está el problema" es la observabilidad. Es lo que convierte el caos de producción en algo entendible.
Stack 2026: Prometheus 3.9 · Grafana 13 · OpenTelemetry · Loki · Grafana Alloy.
F.1 · Monitoreo vs Observabilidad
- Monitoreo: vigilar cosas que ya sabes que pueden fallar (¿CPU alta? ¿app caída?). Responde preguntas conocidas.
- Observabilidad: poder entender cualquier estado del sistema a partir de sus datos, incluso problemas que nunca imaginaste. Responde preguntas que no sabías que tendrías.
🧠 Analogía médica: el monitoreo es la alarma del pulsioxímetro (te avisa si un número se sale de rango). La observabilidad es poder hacerle a tu sistema un análisis completo cuando algo raro pasa, para diagnosticar la causa. Necesitas ambos.
F.2 · Los tres pilares
┌──────────────┬────────────────────────────────┬──────────────────────────┐
│ Pilar │ Qué es │ Herramienta 2026 │
├──────────────┼────────────────────────────────┼──────────────────────────┤
│ 📊 MÉTRICAS │ Números en el tiempo │ Prometheus + Grafana │
│ │ (req/s, latencia, CPU, errores) │ │
│ 📝 LOGS │ Registro de eventos discretos │ Loki + Grafana │
│ │ ("usuario X hizo Y a las Z") │ │
│ 🔍 TRAZAS │ El viaje de UNA petición a │ OpenTelemetry + Tempo/ │
│ │ través de todos los servicios │ Jaeger │
└──────────────┴────────────────────────────────┴──────────────────────────┘Cada pilar responde algo distinto: métricas → "¿algo va mal y cuánto?"; logs → "¿qué pasó exactamente?"; trazas → "¿dónde, en qué servicio/función, se atascó?".
F.3 · Logs estructurados (empieza por aquí)
El primer paso, y el más rentable: logs en JSON con contexto, no print() sueltos.
# ❌ Log plano: imposible de filtrar/buscar a escala
print(f"Usuario {id} hizo login")
# ✅ Log estructurado: buscable, filtrable, con contexto
import structlog
log = structlog.get_logger()
log.info("login_exitoso", usuario_id=42, ip="203.0.113.5", duracion_ms=87)
# → {"event":"login_exitoso","usuario_id":42,"ip":"203.0.113.5","duracion_ms":87,"ts":"..."}💡 Por qué JSON: puedes buscar "todos los logins de la IP X" o "todas las peticiones que tardaron más de 1s" con una consulta, en vez de hacer
grepa ciegas sobre texto. A escala (miles de peticiones/min), los logs planos son inservibles.
⚠️ NUNCA loguees datos sensibles (contraseñas, tokens, números de tarjeta, datos personales completos). Los logs se almacenan, se copian y se leen por mucha gente. Un log con contraseñas es una filtración esperando a ocurrir (apéndice C).
Añade un request ID (correlation ID) a cada petición y propágalo por todos los logs: así sigues una petición concreta entre servicios.
F.4 · Métricas con Prometheus
Prometheus recoge métricas numéricas de tu app cada X segundos (modelo pull: él te pregunta). Tu app expone un endpoint /metrics:
# FastAPI con prometheus-client
from prometheus_client import Counter, Histogram, make_asgi_app
peticiones = Counter("http_requests_total", "Peticiones", ["metodo", "ruta", "codigo"])
latencia = Histogram("http_request_duration_seconds", "Latencia", ["ruta"])
@app.middleware("http")
async def medir(request, call_next):
with latencia.labels(request.url.path).time():
resp = await call_next(request)
peticiones.labels(request.method, request.url.path, resp.status_code).inc()
return resp
app.mount("/metrics", make_asgi_app()) # Prometheus lee de aquíLos 4 tipos de métrica:
| Tipo | Qué mide | Ejemplo |
|---|---|---|
| Counter | Solo sube | Total de peticiones, errores |
| Gauge | Sube y baja | Conexiones activas, uso de memoria |
| Histogram | Distribución | Latencia (p50, p95, p99) |
| Summary | Similar a histogram | Percentiles calculados en cliente |
🧠 Las "4 golden signals" de Google (SRE) — lo mínimo que debes medir de un servicio: Latencia (cuánto tarda), Tráfico (cuántas peticiones), Errores (cuántas fallan) y Saturación (cuán lleno está: CPU/RAM). Si mides estas 4, ya sabes la salud de tu app.
💡 Fíjate en p95/p99, no en el promedio. El promedio miente: si 99 peticiones tardan 10ms y una tarda 5s, el "promedio" parece bueno pero un usuario tuvo una experiencia horrible. El percentil 95 (p95) te dice "el 95% de los usuarios tuvo esta latencia o mejor" — mucho más útil.
F.5 · Dashboards con Grafana
Grafana (v13 en 2026) es la interfaz que dibuja tus métricas de Prometheus (y logs de Loki, y trazas) en gráficas. Se consultan con PromQL:
# Peticiones por segundo, por ruta
rate(http_requests_total[5m])
# Latencia p95
histogram_quantile(0.95, rate(http_request_duration_seconds_bucket[5m]))
# Tasa de errores (% de 5xx)
sum(rate(http_requests_total{codigo=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m]))💡 Grafana Alloy (2026): un colector unificado que ingiere métricas (Prometheus), logs (Loki), trazas y perfiles con una sola configuración. Simplifica montar todo el stack; en Grafana 13 es la forma recomendada de empezar.
Un stack de observabilidad típico en compose.yaml (cap. 12):
services:
prometheus:
image: prom/prometheus:v3.9.0
volumes: ["./prometheus.yml:/etc/prometheus/prometheus.yml:ro"]
ports: ["9090:9090"]
grafana:
image: grafana/grafana:13.0.0
ports: ["3000:3000"]
depends_on: [prometheus]F.6 · Trazas distribuidas con OpenTelemetry
Cuando una petición pasa por varios servicios (API → auth → BD → servicio de pago), una traza muestra el viaje completo y dónde se fue el tiempo:
Petición POST /pedido ────────────────────────────────── 340ms total
├─ auth.verificar_token ──── 12ms
├─ inventario.comprobar ────────── 45ms
├─ db.guardar_pedido ────────────────────── 180ms ⚠️ ¡aquí está el cuello de botella!
└─ pago.procesar ────────── 90msOpenTelemetry (OTel) es el estándar vendor-neutral de 2026 para instrumentar tu código (métricas + logs + trazas con una sola librería). Envías los datos a Grafana Tempo, Jaeger o cualquier backend, sin atarte a un proveedor:
from opentelemetry import trace
tracer = trace.get_tracer(__name__)
async def procesar_pedido(datos):
with tracer.start_as_current_span("procesar_pedido") as span:
span.set_attribute("pedido.total", datos.total)
with tracer.start_as_current_span("guardar_en_bd"):
await guardar(datos) # este tramo aparece anidado en la traza🧠 Por qué OpenTelemetry ganó: antes cada herramienta (Datadog, New Relic…) tenía su propia librería; cambiar de proveedor significaba reinstrumentar todo. OTel es un estándar único: instrumentas una vez y envías a donde quieras. La industria migró en masa a OTel; es la apuesta segura en 2026.
F.7 · Alertas (que te avisen antes que los usuarios)
De nada sirven las métricas si nadie mira el dashboard. Las alertas te avisan automáticamente:
# Regla de alerta en Prometheus
groups:
- name: api
rules:
- alert: TasaErroresAlta
expr: |
sum(rate(http_requests_total{codigo=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m])) > 0.05
for: 5m
annotations:
summary: "Más del 5% de errores 5xx durante 5 minutos"Se envían a Slack, email, PagerDuty, Telegram… Para errores de aplicación (excepciones con stacktrace y contexto), Sentry (cap. 14) es el complemento perfecto.
⚠️ Cuidado con la fatiga de alertas. Si te llegan 200 alertas al día, dejarás de mirarlas y se te escapará la importante. Alerta solo sobre síntomas que afectan al usuario (errores, latencia alta, servicio caído), no sobre cada métrica que se mueve. Una alerta = algo que requiere acción humana ahora.
F.8 · SLO / SLI (madurez de operación)
Cuando quieras profesionalizar la fiabilidad:
- SLI (Service Level Indicator): una métrica de salud. Ej: "% de peticiones < 200ms".
- SLO (Service Level Objective): la meta. Ej: "99,9% de peticiones < 200ms al mes".
- Error budget: el 0,1% restante es tu "presupuesto de fallo". Si lo gastas, frenas features y te enfocas en estabilidad.
💡 Esto es nivel avanzado (SRE — Site Reliability Engineering). No lo necesitas al empezar, pero saber que existe te da el marco mental: la fiabilidad se mide y se presupuesta, no es un "ojalá no se caiga".
F.9 · Por dónde empezar (orden realista)
No montes todo el stack el día 1. Progresión sensata:
1. Logs estructurados (JSON) + un request ID. ← empieza aquí, hoy
2. Sentry para capturar errores/excepciones. ← te avisa cuando algo peta
3. Healthcheck /salud + monitor externo (UptimeRobot). ← sabes si estás caído
4. Métricas básicas (/metrics) + Prometheus + Grafana. ← las 4 golden signals
5. Alertas sobre errores y latencia. ← que te avisen antes que el cliente
6. OpenTelemetry + trazas. ← cuando tengas varios servicios
7. SLO/error budget. ← madurez de equipoF.10 · Correlación logs-trazas: el trace_id en cada log
Los tres pilares (F.2) por separado ya ayudan, pero su verdadero poder aparece cuando se conectan. El puente es simple: mete el trace_id de OpenTelemetry en cada línea de log.
from opentelemetry import trace
async def procesar_pedido(datos):
span = trace.get_current_span()
trace_id = format(span.get_span_context().trace_id, "032x")
log.info("procesando_pedido", pedido_id=datos.id, trace_id=trace_id)
# ...
# si algo falla, el log de error lleva el MISMO trace_id
log.error("pago_rechazado", pedido_id=datos.id, trace_id=trace_id, motivo="fondos_insuficientes")Flujo real de depuración con esto activado:
1. Una alerta (F.7) dice "tasa de errores alta en /pedido".
2. Filtras logs por severidad=error en esa ventana → ves el trace_id de un caso concreto.
3. Buscas ESE trace_id en Grafana Tempo/Jaeger → ves la traza completa: qué servicio,
qué línea de código, cuánto tardó cada tramo, justo antes de fallar.🧠 Sin esta correlación, pasar de "una alerta de error 5xx" a "la traza exacta que la causó" exige adivinar por timestamp aproximado — con
trace_idcompartido, es una búsqueda exacta. Es la diferencia entre depurar producción a las 3 AM en minutos o en horas. Grafana (F.5) incluso enlaza automáticamente de un log a su traza si el campo se llamatrace_id/traceID.
💡 La mayoría de SDKs de OpenTelemetry para Python/Node/Go tienen auto-instrumentación de logging que inyecta el
trace_idsin que lo escribas a mano en cada log — revisaopentelemetry-instrumentation-logging(Python) o el equivalente de tu stack antes de hacerlo manualmente en cada línea.
✅ Ejercicio del apéndice
Sobre una de tus APIs (dockerizada, cap. 12):
1. Cambia tus logs a JSON estructurado con un request ID por petición.
2. Expón /metrics con contador de peticiones e histograma de latencia.
3. Levanta Prometheus + Grafana en compose.yaml y crea un dashboard con las
4 golden signals (latencia p95, tráfico, errores, saturación).
4. Crea una alerta: tasa de 5xx > 5% durante 5 min.
5. Integra Sentry y provoca un error a propósito para verlo llegar.
6. (Avanzado) Instrumenta con OpenTelemetry y observa una traza con sus tramos.💡 Pistas
- El request ID del punto 1 se genera al entrar la petición (middleware) y se propaga a TODOS los logs de esa petición — inclúyelo también en la respuesta (cabecera
X-Request-Id) para poder correlacionar lo que ve el cliente con lo que ves en tus logs. - Para el histograma de latencia del punto 2, usa buckets razonables para tu API (ej. 10ms, 50ms, 100ms, 500ms, 1s, 5s) — un histograma con buckets mal elegidos es casi tan inútil como no tener métrica.
- La alerta del punto 4 debe usar una ventana de tiempo (5 min), no un umbral instantáneo — un solo 5xx aislado no debería despertar a nadie a las 3 AM.
🧠 Autoevaluación
Monitoreo es vigilar métricas que YA decidiste que importan (dashboards predefinidos, alertas sobre umbrales conocidos). Observabilidad es poder responder preguntas que NO anticipaste haciendo consultas ad-hoc sobre logs, métricas y trazas — la diferencia entre "sé que algo va mal" y "puedo averiguar exactamente qué y por qué".
Sin él, una petición que pasa por varios servicios (API → cola → worker → base de datos dan logs sueltos, imposibles de correlacionar entre sí. Con un id único generado al entrar y propagado a cada log, puedes filtrar por ese id y reconstruir el recorrido completo de una petición específica.
Porque los logs responden "¿qué pasó exactamente en ESTA petición?" mientras que las métricas responden "¿cómo está el sistema en general?" — cuando algo falla, necesitas poder investigar el caso concreto antes de que tenga sentido invertir en dashboards agregados. Es la base sobre la que se apoya todo lo demás.
Una traza conecta explícitamente las llamadas entre servicios como un árbol con tiempos por tramo (span): ves cuánto tardó cada paso de una petición que cruzó 4 servicios, en una sola vista. Logs sueltos por servicio requieren correlacionar manualmente por timestamp o request ID; una traza te da la relación causal ya armada.
La capacidad de saltar directamente de una alerta a la causa raíz. Sin un trace_id común en cada log, pasar de "hay errores 5xx en /pedido" a "esta traza concreta explica por qué" exige correlacionar por timestamp aproximado a mano. Con el trace_id inyectado en los logs, es una búsqueda exacta de un pilar al otro — la diferencia entre minutos y horas al depurar un incidente en producción.
Volver al: README.md · Relacionado: 14-servidores-devops.md, G-kubernetes.md