Grafana Loki Log Aggregation
Vibe Prompt
“Help me set up Grafana Loki + Promtail to collect logs from every Pod in a Kubernetes cluster, and query them in Grafana.”
What is Loki?
Loki is a horizontally scalable, highly available log aggregation system inspired by Prometheus. Unlike traditional log stacks such as ELK, Loki filters logs by labels rather than indexing every log line. This design keeps storage costs low while still enabling powerful, label‑based queries.
Why Use Loki?
- Cost Efficiency: Loki stores raw log streams without full-text indexing, reducing storage by roughly 90 % compared to Elasticsearch.
- Seamless Grafana Integration: A single Grafana instance can display metrics from Prometheus and logs from Loki side‑by‑side.
- Operational Simplicity: Promtail, Loki’s lightweight agent, automatically discovers and streams logs from Kubernetes containers.
- Business Value: Faster debugging, reduced storage spend, and unified observability reduce mean time to resolution (MTTR) and improve uptime.
How to Deploy Loki, Promtail, and Grafana with Docker Compose
Docker Compose File
services:
loki:
image: grafana/loki:latest
ports:
- "3100:3100"
command: -config.file=/etc/loki/local-config.yaml
volumes:
- ./loki-config.yaml:/etc/loki/local-config.yaml
promtail:
image: grafana/promtail:latest
volumes:
- /var/log:/var/log
- ./promtail-config.yaml:/etc/promtail/config.yaml
command: -config.file=/etc/promtail/config.yaml
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
GF_SECURITY_ADMIN_PASSWORD: admin
depends_on:
- loki
Step‑by‑Step Implementation
-
Create a working directory
mkdir loki-demo && cd loki-demo -
Add the
docker-compose.ymlfile
Paste the YAML above intodocker-compose.yml. -
Create Loki configuration (
loki-config.yaml)auth_enabled: false server: http_listen_port: 3100 ingester: wal: enabled: true schema_config: configs: - from: 2020-10-15 store: boltdb-shipper object_store: filesystem schema: v11 index: prefix: index_ period: 168h storage_config: boltdb_shipper: active_index_directory: /data/loki/index cache_location: /data/loki/cache shared_store: filesystem filesystem: directory: /data/loki/chunks limits_config: enforce_metric_name: false reject_old_samples: true reject_old_samples_max_age: 168h chunk_store_config: max_look_back_period: 0s table_manager: retention_deletes_enabled: true retention_period: 720h -
Create Promtail configuration (
promtail-config.yaml)server: http_listen_port: 9080 grpc_listen_port: 0 positions: filename: /tmp/positions.yaml clients: - url: http://loki:3100/loki/api/v1/push scrape_configs: - job_name: kubernetes-pods kubernetes_sd_configs: - role: pod relabel_configs: - source_labels: [__meta_kubernetes_pod_label_app] target_label: app - source_labels: [__meta_kubernetes_namespace] target_label: namespace - source_labels: [__meta_kubernetes_pod_name] target_label: pod - source_labels: [__meta_kubernetes_pod_container_name] target_label: container - source_labels: [__meta_kubernetes_pod_container_name] target_label: job - source_labels: [__meta_kubernetes_pod_container_name] target_label: __path__ replacement: /var/log/containers/*${1}.log -
Start the stack
docker compose up -d -
Verify services
- Loki:
http://localhost:3100/ready→ should return{"status":"success"} - Promtail: logs should appear in the Docker logs (
docker logs promtail) - Grafana:
http://localhost:3000→ login withadmin / admin
- Loki:
Promtail Config Explained
scrape_configs:
- job_name: system
static_configs:
- targets: [localhost]
labels:
job: varlogs
__path__: /var/log/*log
job_name: Identifier for the scrape job.static_configs: Explicitly lists log file paths.labels: Attach metadata to each log stream.job: Logical grouping.__path__: The actual file path pattern Promtail watches.
In Kubernetes, Promtail uses service discovery (kubernetes_sd_configs) to automatically discover pod logs, eliminating the need for static paths.
LogQL – Loki’s Query Language
LogQL blends PromQL’s label filtering with powerful log content filtering.
| Query | What It Does | Why It Matters |
|-------|--------------|----------------|
| {app="my-app"} | Select all logs tagged with app=my-app. | Quickly isolate logs for a specific microservice. |
| {app="my-app"} |= "error" | Filter those logs to lines containing the string “error”. | Spot error events without scanning every line. |
| {app="my-app"} |= "error" != "heartbeat" | Exclude lines that also contain “heartbeat”. | Reduce noise from routine health checks. |
| {app="my-app"} |~ "error|exception|failed" | Regex match for multiple patterns. | Catch a broader set of failure indicators. |
| {app="my-app"} | json | status >= 500 | Parse JSON logs and filter by status code. | Detect HTTP 5xx responses in structured logs. |
Example: Correlating Metrics and Logs
{namespace="production", pod=~"api-server.*"}
|= "error"
!= "timeout"
| json
| line_format "{{.message}}"
- Step 1: Find pods in the
productionnamespace whose names matchapi-server.*. - Step 2: Filter for log lines containing “error” but not “timeout”.
- Step 3: Parse JSON to extract structured fields.
- Step 4: Display only the
messagefield for readability.
Grafana Explore Workflow
- Open Grafana → Navigate to Explore.
- Select Data Source → Choose Loki.
- Enter LogQL Query → Paste or build your query.
- Adjust Time Range → Use the time picker to focus on relevant periods.
- Inspect Results → Review log lines, apply filters, or export data.
Tips for Efficient Exploration
- Use label filters first to narrow the dataset before applying content filters.
- Leverage
|=,!=,|~operators for string matching. - Combine
| jsonwith| line_formatto extract and display specific fields. - Save frequently used queries as Dashboards for quick access.
Key Takeaways
- ✅ Loki is a Prometheus‑inspired log system that indexes only labels, not full text.
- ✅ LogQL merges PromQL label syntax with log content filtering, enabling expressive queries.
- ✅ Seamless Integration: One Grafana instance can display metrics from Prometheus and logs from Loki side‑by‑side.
- ✅ Cost Advantage: Loki’s storage cost is roughly 1/10 of Elasticsearch’s for similar workloads.
- ✅ Operational Simplicity: Promtail auto‑discovery in Kubernetes eliminates manual configuration.
Prometheus vs. Loki
| Feature | Prometheus | Loki | |---------|------------|------| | Data Type | Numerical metrics | Text logs | | Indexing | Label + TSDB | Label + raw log stream | | Query Language | PromQL | LogQL (PromQL‑based) | | Storage Cost | Low | Very low (no full‑text index) | | Primary Use | Monitoring & alerting | Debugging & auditing |
Common Pitfalls & Troubleshooting
| Issue | Symptom | Fix |
|-------|---------|-----|
| Promtail cannot find logs | No logs appear in Grafana | Verify __path__ matches actual log file locations; check container log directories. |
| Loki returns “no data” | Query yields empty result | Ensure labels in query match those emitted by Promtail; use label_values(app) to list available labels. |
| High memory usage | Loki container crashes | Tune ingester and chunk_store_config settings; consider increasing max_look_back_period. |
| Slow query performance | LogQL queries take seconds | Add more labels to narrow search; avoid |~ regex on large streams. |
| Grafana data source error | “Unable to connect to Loki” | Confirm Loki is listening on port 3100; check network policies in Kubernetes. |
Code Example: Sending Logs from a Python Service
import logging
import json
import requests
# Configure logger
logger = logging.getLogger("my-app")
logger.setLevel(logging.INFO)
handler = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
handler.setFormatter(formatter)
logger.addHandler(handler)
# Sample log entry
log_entry = {
"timestamp": "2024-07-04T12:00:00Z",
"level": "ERROR",
"message": "Failed to connect to database",
"status": 500
}
logger.info(json.dumps(log_entry))
When running inside a Kubernetes pod, Promtail will capture this log and forward it to Loki. In Grafana, you can query {app="my-app"} | json | status >= 500.
Loki: A Lightweight Log System
Loki’s design philosophy is to be resource‑efficient while still providing powerful observability. By indexing only labels, Loki keeps the write path fast and the storage footprint small.
Loki vs. ELK Stack
| Aspect | Loki | ELK (Elasticsearch + Logstash + Kibana) | |--------|------|----------------------------------------| | Indexing | Label‑only | Full‑text | | Storage Cost | Low | High | | Query Language | LogQL | Lucene | | Grafana Integration | Native | Requires plugin | | Operational Overhead | Minimal | Significant (Elasticsearch cluster, Logstash pipelines) | | Use Case | Microservice logs, structured logs | Enterprise log analytics, SIEM |
Next Chapter Preview: OpenTelemetry
Logs provide a snapshot of what happened, but to understand how an event propagated through a distributed system, you need distributed tracing. The upcoming chapter will introduce OpenTelemetry, the open source standard for collecting traces, metrics, and logs across services. You’ll learn how to instrument your code, export traces to Loki or Tempo, and visualize end‑to‑end request flows in Grafana. This knowledge will empower you to pinpoint latency bottlenecks, trace root causes, and deliver faster, more reliable services.