Log Aggregation
Centralize logs from all services in a single aggregator with retention, search, and alerting.
Why
Logs on the local disk are invisible during an incident — the pod that crashed is gone, the auto-scaler terminated the instance, and the on-call can't SSH into a box that doesn't exist. A log aggregator (Loki, Datadog, ELK, CloudWatch) ingests logs from all services in real time, so the logs survive the pod death and are queryable from a single pane. This is the difference between 'the logs are gone' and 'query the last 5m of checkout ERROR logs in 50ms'. The aggregator also provides retention (30-90 days for incident forensics), search (structured queries, not grep), and alerting (alert on log patterns, not just metrics). Without aggregation, every incident starts with 'where are the logs?' — with it, the logs are just there, queryable, and the on-call skips 10 minutes of hunting and goes straight to diagnosis.
When
From day one. A service without log aggregation is a service you can't debug in prod.
How
1. Choose an aggregator and ship logs from all services
- Loki (Grafana stack, cheap, good for structured logs), Datadog (paid, full-featured),
ELK (self-hosted, powerful but ops-heavy), CloudWatch (AWS-native)
- Ship via agent (Promtail, Fluentd, Datadog agent) or directly from the app
2. Enforce a retention policy
- 7-30 days hot (queryable, fast), 90 days warm (queryable, slower), 1-7 years cold (compliance)
- Hot retention is expensive; don't keep 90 days hot unless you query that far often
3. Make logs queryable by the fields that matter
- Index or label by: service, level, trace_id, route, status_code
- Don't index everything (expensive); index the fields you query by
4. Build saved queries for common incident patterns
- "checkout errors last 5m": {service="checkout", level="ERROR"} | last 5m
- "slow requests": {latency_ms > 1000} | last 1h
- The on-call runs a saved query, not a custom one, during an incident
5. Alert on log patterns, not just metrics
- "ERROR rate > 1/s for 5m" → page
- "specific error message appears" → page (e.g., "DB connection refused")
- Logs catch failures that metrics miss (e.g., a rare error that doesn't move the error-rate metric)Example
# Log aggregation architecture:
App → logger (JSON) → Promtail → Loki → Grafana (query/dashboard/alert)
↓
Datadog agent → Datadog (if using both)
# Retention policy (Loki):
retention:
hot: 30 days # fast queries, expensive storage
warm: 90 days # slower queries, cheaper storage
cold: 7 years # compliance, rarely queried
# Saved queries (Grafana):
- name: "checkout-errors-5m"
query: '{service="checkout", level="ERROR"} |= "" | last 5m'
- name: "slow-requests-1h"
query: '{latency_ms > 1000} | last 1h'
- name: "db-connection-refused"
query: '{msg=~"DB connection refused"} | last 15m'
# Alert on log pattern (not just metric):
- alert: CheckoutDbConnectionRefused
expr: count_over_time({service="checkout", msg=~"DB connection refused"}[5m]) > 0
annotations:
runbook_url: "https://rb/checkout-db-failover"
# This catches the specific failure even if the error-rate metric hasn't breachedCommon Problems
- ⚠Logs on local disk only — the pod crashes, the logs are gone, and the incident starts with 'where are the logs?'
- ⚠No retention policy — logs expire in 3 days, so the postmortem a week later can't see what happened.
- ⚠Indexing every field — storage costs explode; index only the fields you query by (service, level, trace_id).
- ⚠Only metric alerts, no log alerts — a rare error (DB connection refused) doesn't move the error-rate metric but is still a failure; alert on the log pattern too.
Best Practices
- ✓Ship logs from all services to a central aggregator (Loki, Datadog, ELK) in real time; logs survive pod death and are queryable from one pane.
- ✓Set a tiered retention policy (hot 30d, warm 90d, cold 7y for compliance); don't keep everything hot — it's expensive.
- ✓Index only the fields you query by (service, level, trace_id, route); build saved queries for common incident patterns so the on-call runs, not writes, a query.
- ✓Alert on log patterns (specific error messages, error-rate by service) in addition to metrics; logs catch failures that metrics miss.