← SCRAM AI Lab

Tutoriales

Loki + Grafana para logs de chatbot: query patterns

Aprende a estructurar logs de chatbots en Loki y Grafana sin provocar explosión de cardinalidad: labels correctos, patrones LogQL y control de costos.

May 21, 2026

463 lecturas

Loki + Grafana para logs de chatbot: query patterns

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.

¿Cómo estructurar un log de chatbot para que Loki no colapse en producción?

Para que Loki no colapse, debes tratarlo como un índice de metadatos estáticos y no como un motor de búsqueda de texto completo. La regla operativa consiste en asignar únicamente etiquetas fijas de baja cardinalidad a nivel de stream y volcar la carga variable —como identificadores de usuario, prompts y consumo de tokens— dentro del payload JSON para evaluarlo en tiempo de consulta.

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.

Ejemplo de payload estructurado para ingesta

Un registro estándar emitido en formato JSON por tu servicio backend (FastAPI, NestJS o Go) debe lucir de la siguiente forma antes de pasar por Promtail o Vector:

{
  "timestamp": "2025-03-28T14:30:00Z",
  "service": "chat-api",
  "org_id": "tenant_982",
  "tier": "tier_1",
  "provider": "anthropic",
  "latency_bucket": "fast",
  "user_id": "usr_bc41",
  "session_id": "sess_81093",
  "request_id": "req_fa49e2",
  "model": "claude-3-5-sonnet-20241022",
  "tokens_in": 840,
  "tokens_out": 195,
  "latency_ms": 620,
  "cost_usd": 0.005445,
  "status_code": 200
}

En el pipeline de ingesta, configuras al agente para promover únicamente los primeros cinco campos como etiquetas de stream de Loki, dejando intacto el resto en el cuerpo del mensaje.

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]
    )
  )
)

Comparativa: Arquitecturas de logging para chatbots de IA

Elegir la plataforma correcta impacta directamente el consumo de memoria en tus nodos y la factura mensual de infraestructura. Esta tabla sintetiza las alternativas comunes frente a cargas de trabajo generativas:

Criterio Grafana Loki Elasticsearch / OpenSearch SaaS (Datadog / New Relic)
Modelo de indexación Solo metadatos (streams con labels fijos) Índice invertido en todos los campos de texto Propietario, indexación selectiva configurable
Consumo de memoria / disco Muy bajo (índices diminutos en chunks comprimidos) Alto (el índice suele superar el tamaño de los datos crudos) Nulo en infraestructura propia (delegado al vendor)
Costo de almacenamiento a largo plazo Bajo (almacena chunks directamente en S3 o GCS) Medio-alto (requiere almacenamiento en bloque tipo EBS o SSD) Alto (escala con retención y volumen de eventos por mes)
Búsqueda en texto libre de prompts Fuerza bruta mediante escaneo secuencial (|=) Instantánea gracias a tokenización de texto Rápida según facetas indexadas
Riesgo operativo principal Explosión de cardinalidad por mal uso de labels OOM por heap saturado y fragmentación de shards Facturas imprevistas por picos de tráfico

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 impacto financiero de la observabilidad en América Latina

Para equipos de ingeniería en México, Colombia, Argentina o Brasil, los costos de observabilidad cotizados en dólares estadounidenses representan una carga presupuestaria desproporcionada. Los modelos tradicionales de cobro por giga ingerido de proveedores SaaS comerciales generan fricción constante conforme escala la adopción de un chatbot conversacional.

De acuerdo con la documentación de precios públicos de Amazon Web Services para la región US-East (N. Virginia), el almacenamiento en Amazon S3 Standard tiene un costo base de 0.023 USD por gigabyte al mes, mientras que las soluciones administradas de monitoreo de logs suelen iniciar arriba de 1.50 a 2.50 USD por millón de eventos. Montar Loki sobre almacenamiento de objetos propio permite a los equipos regionales desacoplar el volumen de interacción del chatbot respecto a la factura de monitoreo, manteniendo los costos controlados aun frente a crecimientos abruptos de tráfico.

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.

Qué puede salir mal al consultar JSON en Loki y cómo mitigarlo

Aunque delegar atributos al payload JSON salva el índice, introduce un riesgo real: las consultas masivas no optimizadas. Si un ingeniero lanza una query sobre un rango de 30 días usando filtros de texto no indexados, los queriers de Loki intentarán descargar y descomprimir cientos de gigabytes de chunks de S3 de golpe, provocando bloqueos o caídas por Out Of Memory (OOM).

Para mitigar este problema en entornos de producción:

  • Obliga siempre a usar labels en la primera etapa de la expresión: Nunca arranques con {} | json. Acota primero con {service="chat-api", provider="openai"} para que el querier solo descargue los streams estrictamente necesarios.
  • Aplica filtros de línea antes del parser JSON: Los filtros de línea (|= "error_code_500") se ejecutan en texto plano mediante Rust/C-bindings a alta velocidad. Filtrar primero el texto plano y ejecutar después el | json reduce el costo computacional hasta en un 80%.
  • Configura límites de paralelismo en Grafana: En el archivo de configuración loki.yaml, ajusta max_query_length y limita max_query_parallelism para impedir que una búsqueda desmedida monopolice los recursos del cluster.

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