๐Ÿ“Š

Observabilidad

๐Ÿ‘จโ€๐Ÿณ Chef

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      โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
PilarPregunta que respondeEjemplo
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

NivelCuando usarloEjemplo
errorAlgo fallo y necesita atencionDB connection lost
warnAlgo raro pero no criticoRate limit casi alcanzado
infoEventos importantes del negocioUsuario creo orden
debugDetalles para troubleshootingQuery 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

TipoDescripcionEjemplo
CounterSolo incrementaTotal de requests, errores
GaugeSube y bajaConexiones activas, temperatura
HistogramDistribucion de valoresLatencia (p50, p95, p99)
SummarySimilar a histogramPercentiles 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)

ServicioEspecialidadTier Gratis
Grafana CloudMetricas + Logs + Traces10k series, 50GB logs
DatadogAll-in-one observabilidadTrial 14 dias
New RelicAPM completo100GB/mes gratis
SentryErrores y performance5k 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


Recursos