6/7/2026 • Observabilidad • 7 min de lectura

Loki con Grafana: logs, LogQL y dashboard práctico

Una guía práctica para montar Loki con Grafana y Alloy, enviar logs estructurados, consultar con LogQL, crear alertas e importar un dashboard operativo.

#loki#grafana#logs#observability#logql

Loki es el sistema de logs de Grafana Labs. A diferencia de muchas soluciones de logging, no indexa el contenido completo del log: indexa labels y almacena el texto del log de forma eficiente. Eso lo hace especialmente útil para logs operativos, debugging y correlación con métricas y trazas.

Este tutorial es práctico: vas a levantar Loki, Grafana y Grafana Alloy con Docker Compose; vas a enviar logs estructurados; vas a consultar con LogQL; vas a crear alertas; y vas a importar un dashboard de Grafana listo para usar.

Dashboard incluido:

Modelo mental

El flujo de Loki es:

aplicaciones escriben logs
collector lee logs
collector añade labels
collector envía a Loki
Loki almacena streams
Grafana consulta con LogQL
dashboards y alertas usan esas consultas

Piezas principales:

Regla clave: en Loki, los labels son poderosos pero peligrosos. No pongas valores de alta cardinalidad como request_id, user_id, email o trace_id como labels. Déjalos dentro del log y extráelos en consulta cuando haga falta.

Stack local con Docker Compose

Estructura:

observability-logs/
  compose.yml
  loki/
    loki.yml
  alloy/
    config.alloy
  grafana/
    provisioning/
      datasources/
        loki.yml
      dashboards/
        dashboards.yml
    dashboards/
      loki-logs-overview.json
  logs/
    app.log

compose.yml:

services:
  loki:
    image: grafana/loki:latest
    command: ["-config.file=/etc/loki/loki.yml"]
    ports:
      - "3100:3100"
    volumes:
      - ./loki/loki.yml:/etc/loki/loki.yml:ro
      - loki_data:/loki

  alloy:
    image: grafana/alloy:latest
    command:
      - run
      - /etc/alloy/config.alloy
      - --server.http.listen-addr=0.0.0.0:12345
    ports:
      - "12345:12345"
    volumes:
      - ./alloy/config.alloy:/etc/alloy/config.alloy:ro
      - ./logs:/var/log/tutorial:ro
    depends_on:
      - loki

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      GF_SECURITY_ADMIN_USER: admin
      GF_SECURITY_ADMIN_PASSWORD: admin
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/provisioning:/etc/grafana/provisioning:ro
      - ./grafana/dashboards:/var/lib/grafana/dashboards:ro
    depends_on:
      - loki

volumes:
  loki_data:
  grafana_data:

Levantar:

docker compose up -d

URLs:

Loki:    http://localhost:3100
Alloy:   http://localhost:12345
Grafana: http://localhost:3000

Configurar Loki

loki/loki.yml:

auth_enabled: false

server:
  http_listen_port: 3100

common:
  path_prefix: /loki
  replication_factor: 1
  ring:
    kvstore:
      store: inmemory
  storage:
    filesystem:
      chunks_directory: /loki/chunks
      rules_directory: /loki/rules

schema_config:
  configs:
    - from: 2024-04-01
      store: tsdb
      object_store: filesystem
      schema: v13
      index:
        prefix: index_
        period: 24h

limits_config:
  allow_structured_metadata: true
  reject_old_samples: true
  reject_old_samples_max_age: 168h

Esta configuración es local y didáctica. Para producción necesitas almacenamiento de objetos, límites revisados, autenticación o gateway, retención, multi-tenancy si aplica y estrategia de backup.

Enviar logs con Grafana Alloy

alloy/config.alloy:

local.file_match "tutorial_logs" {
  path_targets = [
    {
      "__path__" = "/var/log/tutorial/*.log",
      "service_name" = "demo-api",
      "environment" = "local",
    },
  ]
}

loki.source.file "tutorial_logs" {
  targets = local.file_match.tutorial_logs.targets
  forward_to = [loki.process.tutorial_logs.receiver]
}

loki.process "tutorial_logs" {
  stage.json {
    expressions = {
      level = "level",
      message = "message",
      trace_id = "trace_id",
    }
  }

  stage.labels {
    values = {
      level = "level",
    }
  }

  forward_to = [loki.write.default.receiver]
}

loki.write "default" {
  endpoint {
    url = "http://loki:3100/loki/api/v1/push"
  }
}

Puntos importantes:

Generar logs de ejemplo

Crea el directorio:

mkdir -p logs

Escribe logs JSON:

cat >> logs/app.log <<'EOF'
{"ts":"2026-06-23T10:00:00Z","level":"info","message":"server started","trace_id":"abc001","route":"/health","status":200}
{"ts":"2026-06-23T10:00:05Z","level":"warn","message":"slow request","trace_id":"abc002","route":"/api/orders","status":200,"duration_ms":950}
{"ts":"2026-06-23T10:00:10Z","level":"error","message":"database timeout","trace_id":"abc003","route":"/api/orders","status":500,"duration_ms":3000}
EOF

Ver logs de Alloy:

docker compose logs -f alloy

Comprobar labels en Loki:

curl -s http://localhost:3100/loki/api/v1/labels

Provisionar datasource de Grafana

grafana/provisioning/datasources/loki.yml:

apiVersion: 1

datasources:
  - name: Loki
    uid: loki
    type: loki
    access: proxy
    url: http://loki:3100
    editable: true

Provisionar dashboards:

grafana/provisioning/dashboards/dashboards.yml:

apiVersion: 1

providers:
  - name: tutorial-dashboards
    orgId: 1
    folder: Observabilidad
    type: file
    disableDeletion: false
    editable: true
    options:
      path: /var/lib/grafana/dashboards

Copia el dashboard incluido:

mkdir -p grafana/dashboards
cp app/public/assets/grafana-dashboards/loki-logs-overview.json grafana/dashboards/

Importación manual:

LogQL esencial

Consultar logs por servicio:

{service_name="demo-api"}

Filtrar texto:

{service_name="demo-api"} |= "database"

Regex case-insensitive para errores:

{service_name="demo-api"} |~ "(?i)(error|exception|panic|fatal)"

Parsear JSON:

{service_name="demo-api"} | json

Filtrar por campo extraído:

{service_name="demo-api"} | json | status >= 500

Contar logs por nivel:

sum by (level) (count_over_time({service_name="demo-api"} | json | level=~".+" [5m]))

Logs por segundo:

sum by (service_name) (rate({service_name=~".+"}[5m]))

Errores por minuto:

sum by (service_name) (
  rate({service_name=~".+"} |~ "(?i)(error|exception|panic|fatal)" [5m])
)

Extraer duración y calcular p95 si el campo existe:

quantile_over_time(
  0.95,
  {service_name="demo-api"} | json | unwrap duration_ms [5m]
)

Labels: qué sí y qué no

Buenos labels:

service_name
environment
namespace
cluster
level
team

Malos labels:

request_id
trace_id
user_id
email
path completo con IDs
payload hash

Regla práctica: si el valor puede tener miles o millones de combinaciones, no debe ser label. Guárdalo dentro del log y parsea en consulta.

Alertas con Loki

Puedes alertar sobre errores:

sum by (service_name) (
  count_over_time({service_name=~".+"} |~ "(?i)(error|exception|panic|fatal)" [5m])
) > 5

También sobre ausencia de logs, útil para workers:

absent_over_time({service_name="critical-worker"}[10m])

Alerta sobre crecimiento de warnings:

sum by (service_name) (
  rate({service_name=~".+", level="warn"}[5m])
) > 1

Buenas prácticas:

Dashboard de Grafana incluido

El dashboard creado para este post incluye:

Importación:

  1. Abre Grafana.
  2. Ve a Dashboards.
  3. Pulsa New y luego Import.
  4. Sube loki-logs-overview.json.
  5. Selecciona el datasource Loki.
  6. Pulsa Import.

Si tus labels no usan service_name o level, ajusta las queries del dashboard.

Correlación con trazas

Si tus logs tienen trace_id, puedes enlazar desde Loki hacia Tempo en Grafana. La idea es:

log line contiene trace_id
Grafana extrae trace_id
link abre la traza correspondiente en Tempo

Ejemplo de log:

{"level":"error","message":"database timeout","trace_id":"abc003","route":"/api/orders"}

Recomendación:

Debugging

Ver estado de Loki:

curl -s http://localhost:3100/ready

Ver labels:

curl -s http://localhost:3100/loki/api/v1/labels

Consultar por API:

curl -G -s "http://localhost:3100/loki/api/v1/query" \
  --data-urlencode 'query={service_name="demo-api"}'

Ver logs del collector:

docker compose logs -f alloy

Ver logs de Loki:

docker compose logs -f loki

Problemas frecuentes:

Checklist profesional

Glosario

Alloy: collector de Grafana para métricas, logs, trazas y perfiles.

Loki: backend de logs de Grafana Labs.

LogQL: lenguaje de consulta de Loki.

Stream: conjunto de logs con los mismos labels.

Label: dimensión indexada usada para seleccionar streams.

Parser: etapa que extrae campos del contenido del log.

Derived field: campo extraído en Grafana para enlazar logs con otros sistemas, como Tempo.

Cardinalidad: número de combinaciones únicas de labels.

Referencias oficiales

Cierre

Loki funciona mejor cuando los logs están estructurados y los labels están diseñados con disciplina. Un buen stack de logs no consiste en indexarlo todo: consiste en conservar contexto útil, controlar cardinalidad, consultar rápido, alertar sobre síntomas accionables y conectar logs con métricas y trazas cuando necesitas investigar incidentes reales.