Estado: ✅ Aceptada Fecha: 2026-05-18 Contexto que la motiva: bloque “logs estructurados y primeras métricas del server” de Fase 2 → primer paso de observabilidad operacional para gabysql-server.

🧭 Contexto

Hasta este bloque, gabysql-server corría silencioso: el único output a stdout/stderr era el banner de arranque (gabysql-server escuchando en ...) y los accept error esporádicos. Nada por request, nada de contadores agregados.

Operacionalmente esto rompía dos cosas:

  1. Diagnóstico post-mortem: si un usuario reportaba “el server se puso lento ayer a las 3pm”, no había forma de mirar atrás. No había logs, no había histograma de latencia, nada.
  2. Health más allá de /health: el /health actual solo dice “estoy vivo y configurado”. No responde “¿cómo se está comportando bajo carga?”. Para un operador con un dashboard, el binario en producción es opaco.

Restricciones del proyecto:

💡 Decisión

Tres piezas en src/server.rs, todas zero-deps:

1. Metrics in-memory acotado

pub struct Metrics {
    started_unix: u64,
    requests_by_status: HashMap<u16, u64>,
    errors_total: u64,
    latency_samples: Vec<u32>,  // ring buffer, cap LATENCY_SAMPLE_RING = 1024
    latency_cursor: usize,
    latency_count: u64,
}

2. Endpoint GET /metrics

Devuelve JSON estable:

{
  "ok": true,
  "started_unix": 1747497600,
  "uptime_s": 3600,
  "requests_total": 1234,
  "requests_by_status": {"200": 1180, "400": 30, "500": 24},
  "errors_total": 24,
  "latency_ms": {"p50": 5, "p95": 87, "samples": 1024, "count": 1234}
}

Gated por auth como cualquier otro endpoint cuando hay -token. Sin cuerpo de request, sin parámetros.

3. Logs JSON opt-in

Flag nuevo -log-json en gabysql-server. Cuando está activo, cada request termina escribiendo una línea JSON a stdout:

{"ts_unix":1747497612,"method":"POST","path":"/exec","status":200,"latency_ms":12}

stderr sigue siendo el banner de arranque + errores de accept; stdout queda como stream de eventos procesable por jq, tee, ingest a S3/ELK/Loki, etc.

Por defecto off. La UX humana del binario no cambia.

🤔 Alternativas evaluadas

  1. tracing + tracing-subscriber: el ecosistema canónico. Pero viola ADR-0001 (deps externas) y trae 10-20 transitivas más. No vale por una línea JSON por request.

  2. Logs a archivo con rotación: agrega complejidad (rotation policy, locks, fsync) sin ganar nada que stdout+pipe-a-archivo no resuelva ya con herramientas del SO (logrotate, multilog, supervisor).

  3. Histograma exponencial real (HdrHistogram, t-digest): más preciso para tails extremos pero requiere implementación manual no-trivial. El ring buffer de 1024 sortable da p50/p95 con error < 0.5% para distribuciones razonables — suficiente para esta fase.

  4. /metrics en formato Prometheus text (# HELP, # TYPE, metric_name{label="x"} value): Prometheus es popular, pero (a) el resto del API ya es JSON, mezclar dos formatos sin razón es sucio; (b) un adapter Prometheus de tercero puede traducir nuestro JSON cuando alguien lo necesite. Mantener un solo content-type es más simple.

  5. Métricas persistentes (a disco entre reinicios): agrega filesystem state que no aporta para el caso “operador mira el dashboard ahora mismo”. Si alguien quiere histórico, hace scrape periódico al endpoint y guarda fuera.

✅ Consecuencias

Positivas:

Negativas / a vigilar:

🔗 Referencias