El problema de la caja negra
Imagina que manejas un auto sin tablero de instrumentos. No sabes a que velocidad vas, cuanto combustible te queda, ni si el motor esta por sobrecalentarse. Asi es operar una aplicacion en produccion sin observabilidad.
Sin observabilidad, solo te enteras de los problemas cuando tus usuarios se quejan. Con observabilidad, los detectas antes de que los usuarios los noten.
Los tres pilares de la observabilidad
La observabilidad se construye sobre tres pilares complementarios:
โโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโ
โ LOGS โ METRICAS โ TRACES โ
โ โ โ โ
โ ยฟQue paso? โ ยฟCuanto? โ ยฟDonde? โ
โ โ โ โ
โ Eventos โ Numeros que โ Camino de una โ
โ discretos con โ puedes graficarโ request a โ
โ contexto โ y alertar โ traves de โ
โ โ โ servicios โ
โโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโ
| Pilar | Pregunta que responde | Ejemplo |
|---|---|---|
| Logs | ยฟQue paso exactamente? | "Usuario X fallo login por password incorrecto" |
| Metricas | ยฟComo esta el sistema? | "95% de requests < 200ms, 2% errores" |
| Traces | ยฟPor donde paso la request? | "API โ Auth โ DB โ Cache โ Response (340ms)" |
Logs: El registro de eventos
Por que console.log no escala
// En desarrollo: funciona
console.log('Usuario logueado:', userId);
// En produccion con 1000 requests/seg:
// - ยฟComo buscas un log especifico?
// - ยฟComo correlacionas logs de la misma request?
// - ยฟComo filtras solo errores?
// - ยฟComo guardas los logs cuando el servidor muere?
Logging estructurado con Pino
import pino from 'pino';
const logger = pino({
level: process.env.LOG_LEVEL || 'info',
formatters: {
level: (label) => ({ level: label }),
},
timestamp: pino.stdTimeFunctions.isoTime,
});
// Crear logger con contexto de request
function createRequestLogger(req) {
return logger.child({
requestId: req.id,
userId: req.user?.id,
path: req.path,
method: req.method,
});
}
// Uso en endpoint
app.get('/api/orders', async (req, res) => {
const log = createRequestLogger(req);
log.info('Fetching orders');
try {
const orders = await getOrders(req.user.id);
log.info({ count: orders.length }, 'Orders fetched successfully');
res.json(orders);
} catch (error) {
log.error({ error: error.message, stack: error.stack }, 'Failed to fetch orders');
res.status(500).json({ error: 'Internal error' });
}
});
Output JSON estructurado:
{
"level": "info",
"time": "2026-01-15T10:30:00.000Z",
"requestId": "abc-123",
"userId": "user-456",
"path": "/api/orders",
"method": "GET",
"msg": "Orders fetched successfully",
"count": 25
}
Niveles de log
| Nivel | Cuando usarlo | Ejemplo |
|---|---|---|
| error | Algo fallo y necesita atencion | DB connection lost |
| warn | Algo raro pero no critico | Rate limit casi alcanzado |
| info | Eventos importantes del negocio | Usuario creo orden |
| debug | Detalles para troubleshooting | Query ejecutado en 50ms |
Metricas: Numeros que importan
Las 4 Golden Signals (Google SRE)
Google SRE define 4 senales clave para monitorear cualquier servicio:
โโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโ
โ LATENCY โ TRAFFIC โ ERRORS โ SATURATION โ
โ โ โ โ โ
โ ยฟCuanto โ ยฟCuanta โ ยฟQue % โ ยฟQue tan โ
โ tarda? โ demanda? โ falla? โ lleno esta? โ
โ โ โ โ โ
โ p50, p95, โ req/seg โ % HTTP 5xx โ CPU, RAM, โ
โ p99 โ usuarios โ % timeouts โ conexiones โ
โโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโ
Prometheus + prom-client
import promClient from 'prom-client';
// Recolectar metricas por defecto (CPU, memoria, etc.)
promClient.collectDefaultMetrics();
// Metrica custom: latencia de requests
const httpRequestDuration = new promClient.Histogram({
name: 'http_request_duration_seconds',
help: 'Duration of HTTP requests in seconds',
labelNames: ['method', 'route', 'status_code'],
buckets: [0.01, 0.05, 0.1, 0.5, 1, 2, 5],
});
// Metrica custom: requests totales
const httpRequestsTotal = new promClient.Counter({
name: 'http_requests_total',
help: 'Total number of HTTP requests',
labelNames: ['method', 'route', 'status_code'],
});
// Middleware para medir requests
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const duration = (Date.now() - start) / 1000;
const labels = {
method: req.method,
route: req.route?.path || 'unknown',
status_code: res.statusCode,
};
httpRequestDuration.observe(labels, duration);
httpRequestsTotal.inc(labels);
});
next();
});
// Endpoint para Prometheus
app.get('/metrics', async (req, res) => {
res.set('Content-Type', promClient.register.contentType);
res.end(await promClient.register.metrics());
});
Tipos de metricas
| Tipo | Descripcion | Ejemplo |
|---|---|---|
| Counter | Solo incrementa | Total de requests, errores |
| Gauge | Sube y baja | Conexiones activas, temperatura |
| Histogram | Distribucion de valores | Latencia (p50, p95, p99) |
| Summary | Similar a histogram | Percentiles calculados client-side |
Traces: Siguiendo el camino
El problema de microservicios
En un monolito, seguir una request es facil. En microservicios:
Usuario โ API Gateway โ Auth Service โ Order Service โ Payment Service โ DB
โ โ โ โ
ยฟDonde esta el cuello de botella? ๐คท
OpenTelemetry: El estandar
OpenTelemetry es el estandar open source para instrumentacion.
import { NodeSDK } from '@opentelemetry/sdk-node';
import { getNodeAutoInstrumentations } from '@opentelemetry/auto-instrumentations-node';
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
const sdk = new NodeSDK({
serviceName: 'order-service',
traceExporter: new OTLPTraceExporter({
url: 'http://jaeger:4318/v1/traces',
}),
instrumentations: [
getNodeAutoInstrumentations({
'@opentelemetry/instrumentation-http': { enabled: true },
'@opentelemetry/instrumentation-express': { enabled: true },
'@opentelemetry/instrumentation-pg': { enabled: true },
}),
],
});
sdk.start();
Anatomia de un trace
Trace ID: abc-123-xyz
โโโ Span: HTTP GET /api/order/123 (450ms)
โ โโโ Span: Auth Middleware (20ms)
โ โโโ Span: DB Query: SELECT * FROM orders (150ms)
โ โโโ Span: HTTP POST payment-service/charge (250ms)
โ โ โโโ Span: Validate card (30ms)
โ โ โโโ Span: Process payment (220ms)
โ โโโ Span: Send confirmation email (30ms)
Stack recomendado 2026
Open Source (auto-hospedado)
# docker-compose.yml - Stack de observabilidad
services:
# Metricas
prometheus:
image: prom/prometheus:latest
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
# Visualizacion
grafana:
image: grafana/grafana:latest
environment:
- GF_SECURITY_ADMIN_PASSWORD=secret
ports:
- "3000:3000"
volumes:
- grafana-data:/var/lib/grafana
# Logs
loki:
image: grafana/loki:latest
ports:
- "3100:3100"
# Traces
jaeger:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # UI
- "4318:4318" # OTLP HTTP
Servicios Managed (SaaS)
| Servicio | Especialidad | Tier Gratis |
|---|---|---|
| Grafana Cloud | Metricas + Logs + Traces | 10k series, 50GB logs |
| Datadog | All-in-one observabilidad | Trial 14 dias |
| New Relic | APM completo | 100GB/mes gratis |
| Sentry | Errores y performance | 5k errores/mes |
Alerting efectivo
Por que "alertar todo" es contraproducente
Alerta: CPU > 50% โ Ignorada (normal en picos)
Alerta: Memoria > 60% โ Ignorada (siempre asi)
Alerta: Request lento โ Ignorada (ya vimos 100 hoy)
Alerta: Base de datos caida โ Ignorada por habito... OOPS
Resultado: Alert fatigue. El equipo ignora todas las alertas.
SLOs y Error Budgets
En lugar de alertar por sintomas, alerta cuando afectas al usuario:
SLO (Service Level Objective):
"99.9% de requests en menos de 500ms"
Error Budget:
- 30 dias ร 24 horas ร 60 min = 43,200 minutos
- 0.1% de error budget = 43.2 minutos de downtime permitido
Alerta cuando:
- Consumiste > 50% del budget en 1 hora
- Consumiste > 80% del budget en el mes
Anatomia de una buena alerta
# prometheus/alerts.yml
groups:
- name: api-alerts
rules:
- alert: HighErrorRate
expr: |
sum(rate(http_requests_total{status_code=~"5.."}[5m])) /
sum(rate(http_requests_total[5m])) > 0.01
for: 5m # Solo alerta si persiste 5 min
labels:
severity: critical
annotations:
summary: "Error rate > 1% por 5 minutos"
runbook: "https://wiki.tu-empresa.com/runbooks/high-error-rate"
dashboard: "https://grafana.tu-empresa.com/d/api-health"
Checklist de observabilidad
- Logs estructurados con requestId para correlacion
- Metricas de las 4 Golden Signals expuestas
- Traces configurados entre servicios
- Dashboards con las metricas clave
- Alertas basadas en SLOs, no sintomas
- Runbooks documentados para cada alerta
- Retencion de datos definida (30 dias logs, 15 dias traces)
Practica
-> Configurar Prometheus + Grafana -> Logging profesional con Pino + Loki