← Back to list

Grafana for Everyone — Part 6: Loki — Adding Logs to Your Grafana Stack

Metrics tell you a service is slow. Logs tell you why. This part adds Grafana Loki to the stack — and shows how to correlate logs with…

Latha Narayanappa · 2026-06-13 08:30 · 0 claps · 4.2 min read paywalled
#grafana #grafana-loki
Open on Medium ↗

Grafana for Everyone — Part 6: Loki — Adding Logs to Your Grafana Stack

Metrics tell you a service is slow. Logs tell you why. This part adds Grafana Loki to the stack — and shows how to correlate logs with metrics in the same dashboard.

Why Loki?

Before Loki, the standard log stack was Elasticsearch + Kibana (the ELK stack). It works, but it’s heavy: Elasticsearch indexes every field in every log line, which is powerful but expensive — both in compute and operational complexity.

Loki’s philosophy is different: it indexes only metadata (labels), not the log content itself. Log lines are stored compressed. Querying scans and filters the content at read time.

The tradeoff:

  • ✅ Much cheaper to store and operate
  • ✅ Label model matches Prometheus — same labels, unified querying
  • ✅ Native Grafana integration — logs appear in the same dashboard as metrics
  • ❌ Full-text search is slower than Elasticsearch for massive volumes
  • ❌ No field extraction at ingest time (unless you use pipelines)

For most teams running Kubernetes or Docker workloads, Loki hits the sweet spot. For compliance-heavy log analytics at petabyte scale, Elasticsearch may still win.

Architecture

Your Application / Containers
         │ (stdout/stderr)
         ▼
    Promtail / Alloy
    (log shipping agent)
         │ (push)
         ▼
    Loki (storage + query)
         │
         ▼
    Grafana (visualization)

Promtail (or the newer Grafana Alloy) runs as a DaemonSet on each Kubernetes node, tails container logs, attaches labels, and ships to Loki. Loki stores them. Grafana queries Loki and renders them.

Quick Start with Docker Compose

Add Loki and Promtail to your existing stack:

version: "3"
services:
  loki:
    image: grafana/loki:latest
    ports: ["3100:3100"]
    command: -config.file=/etc/loki/local-config.yaml
    volumes:
      - loki-data:/loki
  promtail:
    image: grafana/promtail:latest
    volumes:
      - /var/log:/var/log:ro
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - ./promtail-config.yaml:/etc/promtail/config.yaml
    command: -config.file=/etc/promtail/config.yaml
  grafana:
    image: grafana/grafana-oss:latest
    ports: ["3000:3000"]
volumes:
  loki-data:

promtail-config.yaml:

server:
  http_listen_port: 9080
positions:
  filename: /tmp/positions.yaml
clients:
  - url: http://loki:3100/loki/api/v1/push
scrape_configs:
  - job_name: docker
    docker_sd_configs:
      - host: unix:///var/run/docker.sock
        refresh_interval: 5s
    relabel_configs:
      - source_labels: [__meta_docker_container_name]
        target_label: container
      - source_labels: [__meta_docker_container_image]
        target_label: image

Add Loki as a Data Source

Connections → Data sources → Add data source → Loki

Field Value Name loki-local URL [http://loki:3100](http://loki:3100)

Save & test. You should see “Data source connected and labels found.”

LogQL — The Query Language

LogQL is Loki’s query language. It’s modeled after PromQL but operates on log streams.

Log Stream Selector (required)

Every LogQL query starts with a stream selector — a set of label matchers that identify which log streams to query:

{app="api-server"}
{app="api-server", env="production"}
{app=~"api-.*"}

This is how Loki avoids scanning all logs — it looks up only the matching streams.

Pipeline Stages

After the stream selector, pipe through filters and parsers:

{app="api-server"}
  |= "error"                          # line contains "error"
  != "health check"                   # exclude health check lines
  | json                              # parse JSON log fields
  | status_code >= 500                # filter on parsed field
  | line_format "{{.method}} {{.path}} → {{.status_code}}"

Filter operators:

Operator Meaning |= "string" Line contains string != "string" Line does not contain string |~ "regex" Line matches regex !~ "regex" Line does not match regex

Parsers:

  • | json — parse entire line as JSON, extract fields
  • | logfmt — parse key=value format
  • | pattern "<ip> <method> <path> <status>" — positional pattern extraction
  • | regexp "(?P<status>\\d{3})" — named capture groups

Metric Queries

LogQL can derive metrics from log streams — count log lines, extract numeric fields, and compute rates:

# Count error log lines per minute
sum(rate({app="api-server"} |= "error" [1m])) by (service)
# Extract HTTP status codes and compute error rate
sum(rate({app="nginx"} | json | status_code >= 500 [5m]))
/
sum(rate({app="nginx"} | json [5m]))

These LogQL metric queries can be used in Grafana panels just like PromQL — including in alert rules.

Building a Logs Dashboard

Panel 1: Log Stream (Logs visualization)

{app=~"$service"} |= "$search_term"
  • Visualization: Logs
  • Panel options: Enable Deduplication, Wrap lines
  • Time: Newest first

Add a search_term text box variable (Part 3) to let users filter logs in real time. Type "timeout" in the box → only timeout log lines appear.

Panel 2: Error Rate from Logs

sum by (app) (
  rate({app=~"$service"} |= "ERROR" [5m])
)
  • Visualization: Time series
  • Unit: logs/sec

Panel 3: Log Volume by Level

sum by (level) (
  rate({app=~"$service"} | json | level != "" [5m])
)
  • Visualization: Bar chart (stacked)
  • Shows the distribution of INFO / WARN / ERROR over time

Correlating Logs and Metrics in One Dashboard

This is Grafana’s killer feature for observability.

Layout

Row: "Service Health"
├── [Time series] Error rate (PromQL)
├── [Stat] p95 latency (PromQL)
└── [Time series] Request rate (PromQL)
Row: "Logs"
├── [Logs panel] Error log stream (LogQL)
└── [Time series] Log error rate (LogQL metric)

When you zoom into a time range on any panel, all panels update — both metric and log panels honor the same time range. Spot a latency spike at 14:32 on the Prometheus panel → scroll down → the Loki logs panel shows exactly what was logged at 14:32.

Data Links: Metrics → Logs

Add a data link on your metric panels to jump to a pre-filtered log view:

Title: View logs
URL: /explore?orgId=1&left={"datasource":"loki-local","queries":[{"expr":"{app=\"${__series.name}\"}","refId":"A"}],"range":{"from":"${__from}","to":"${__to}"}}

Click a series on the error rate graph → opens Grafana Explore with logs for that service in the same time window. This is the “click to drill down” pattern that separates great observability platforms from mediocre ones.

Loki in Kubernetes

For Kubernetes, use the official Helm chart:

helm repo add grafana https://grafana.github.io/helm-charts
helm repo update
# Install Loki stack (Loki + Promtail)
helm install loki grafana/loki-stack \
  --namespace monitoring \
  --set grafana.enabled=false \
  --set prometheus.enabled=false

Promtail auto-discovers pods and attaches labels from Kubernetes metadata:

namespace="production"
pod="api-server-7d4f8b-xkq2p"
container="api"
node="ip-10-0-1-45"

Those labels flow through to Loki and appear in LogQL queries — same label model as your Prometheus metrics.

LogQL Best Practices

Always start with a specific stream selector{app="api-server"} before filters. Never query {} (all streams) — it's the equivalent of SELECT * FROM with no WHERE clause on a billion-row table.

Use structured logging — If your app outputs JSON logs, | json unlocks field-level filtering. Unstructured text logs are harder to filter precisely.

Index carefully chosen labels — Loki indexes labels at ingest. High-cardinality labels (request IDs, user IDs) should stay in log content, not labels. Good labels: app, env, namespace, level.

Use \|= "error" before | json — Filter on raw line content first to reduce the set of lines being parsed. Parsing is expensive; pre-filter when possible.

Up Next

Part 7: Provisioning and Dashboard-as-Code

We’ll manage dashboards, data sources, and alert rules as version-controlled YAML and JSON — so your entire Grafana setup is reproducible, reviewable, and deployable via CI/CD.


메타데이터
post_id
f29ccd35da2f
slug
grafana-for-everyone-part-6-loki-adding-logs-to-your-grafana-stack-f29ccd35da2f
url
https://medium.com/@puttt.spl/grafana-for-everyone-part-6-loki-adding-logs-to-your-grafana-stack-f29ccd35da2f
canonical_url
https://medium.com/@puttt.spl/grafana-for-everyone-part-6-loki-adding-logs-to-your-grafana-stack-f29ccd35da2f
author_url
https://medium.com/@puttt.spl
status
ok
fetched_at
2026-06-27 07:40:21