← SCRAM AI Lab

Tutoriales

Loki + Grafana para logs de chatbot: query patterns

Labels útiles (org_id, session_id, tier, provider) sin caer en high-cardinality. LogQL patterns para errores por tier, p99 latency y costos por hora. Retention 30d hot.

May 21, 2026

9 lecturas

El error con Loki es tratarlo como Elastic — y Loki te lo cobra con cardinality explosion

Loki no es Elasticsearch. No indexa el contenido de los logs; indexa labels. Si pones user_id o request_id como label, generas un stream por cada valor único y Loki muere — vimos un cluster crecer de 4GB a 380GB de índices en 48h por meter trace_id como label. Para logs de chatbot LLM, el set correcto de labels es chico, fijo y de baja cardinalidad.

Los 5 labels que importan

  • service: chat-api, chat-worker, ai-router (3-5 valores)
  • org_id: tenant del SaaS (decenas a cientos — aceptable)
  • tier: 1, 2, 3 (los 3 tiers de modelos LLM)
  • provider: anthropic, openai, google (4-5 valores)
  • latency_bucket: fast, medium, slow, timeout (4 valores)

Todo lo demás — user_id, session_id, request_id, tokens, costos — va en el JSON payload del log, no como label. Loki te deja queriarlos con | json sin generar streams nuevos.

LogQL patterns que usamos a diario

Tasa de errores por tier en la última hora:

sum by (tier) (
  rate({service="chat-api", level="error"}[1h])
)

p99 de latencia por provider:

quantile_over_time(0.99,
  {service="chat-api"} | json | unwrap latency_ms [5m]
) by (provider)

Costo acumulado por hora por org:

sum by (org_id) (
  sum_over_time(
    {service="chat-api"} | json | unwrap cost_usd [1h]
  )
)

Top 10 sessions con más tokens de output:

topk(10,
  sum by (session_id) (
    sum_over_time(
      {service="chat-api"} | json | unwrap tokens_out [1h]
    )
  )
)

Retention y storage

  • Hot (S3 standard): 30 días — queries inmediatas, dashboards
  • Cold (S3 Glacier IR): 90 días — auditoría, debugging post-mortem
  • Después: borrar. Si necesitas más, agrega un job que extraiga métricas agregadas a Postgres antes del cutoff

El anti-patrón que mata clusters Loki

Lista de cosas que nunca deben ser labels:

  • session_id (miles de valores por día)
  • user_id (escala con usuarios)
  • request_id / trace_id (uno por request)
  • endpoint con path variables (/users/123/profile != /users/456/profile)
  • error_message dinámico

Todos esos van en el payload JSON. Si necesitas filtrar por ellos, usas | json | session_id="..." en LogQL — más lento que un label pero no destruye el índice.

Alertas que vale la pena tener

  • Error rate por provider > 5% en 5min (provider tirando 429 o 5xx)
  • p99 latency tier 1 > 5s en 10min (router mal seleccionando tier)
  • Costo/hora por org > 3x el promedio de 7d (loop infinito, abuso, prompt injection que evade rate limit)

La pregunta abierta: ¿deberían los logs estructurados de chatbot tener también un span_id de OpenTelemetry para joinear con traces? Sí, y es lo que cuenta el siguiente artículo.

loki
grafana
observability
← Volver a SCRAM AI Lab