Module

Reading logs without drowning

Filtering signal from noise, structured vs unstructured logs, correlating by request ID and timestamp.

The firehose problem

Your service logs 50,000 lines per minute. The alert says “errors elevated.” You open the log viewer and see… everything.

The skill isn’t reading faster. It’s filtering smarter.

Structured vs unstructured

Unstructured logs are printf statements someone wrote at 3pm on a Tuesday:

2024-03-15 02:14:33 ERROR Failed to connect to payment-service after 3 retries

You can grep them. You can read them. You cannot query them reliably at scale.

Structured logs are JSON (or key=value) with consistent fields:

{"level":"error","msg":"payment request failed","request_id":"req_8f3a2b","service":"checkout","downstream":"payment-service","latency_ms":3200,"attempt":3}

Same incident. One you grep. One you filter, aggregate, and alert on.

If your team doesn’t have structured logs yet, that’s a project for next quarter. Tonight you grep.

The three filters that actually help

When you’re paged, start narrow and widen only if you find nothing:

  1. Time window — Last 15 minutes, not last 24 hours. Incidents have a start time. Find it.
  2. LevelERROR and WARN first. INFO is noise until you know what you’re looking for.
  3. Service / host — If the alert names a service, filter to it. Don’t read the whole cluster.
level:error AND service:checkout AND @timestamp:[now-15m TO now]

Adjust syntax for your stack. The pattern is the same.

Correlation: request ID is your lifeline

Distributed systems scatter one user action across a dozen services. The request ID (or trace ID) is how you reassemble the story.

Find one error line. Copy its request_id. Search for that ID across all services:

request_id:req_8f3a2b

Now you see the full path: API gateway → auth → checkout → payment-service → timeout.

Without request ID correlation, you’re reading unrelated errors and guessing they’re connected. With it, you have a timeline.

Timestamp traps

Logs lie about time in three common ways:

  • Timezone mismatch — Dashboard in UTC, logs in local. Always confirm before correlating with a deploy at “02:14.”
  • Clock skew — Hosts drift. A log “before” the deploy might actually be after it.
  • Batching delay — Log shippers buffer. The error happened 30 seconds before it appeared in Kibana.

When correlating logs with metrics, use a window, not a point: “errors started between 02:10 and 02:20.”

Grepping without drowning

For unstructured or SSH-access logs, these patterns cover 80% of 2am debugging:

# Errors in the last N lines
tail -5000 app.log | grep -i error

# Exclude known noise
grep -i error app.log | grep -v "health check" | grep -v "favicon"

# Context around a match (3 lines before and after)
grep -B3 -A3 "payment-service" app.log

# Count errors per minute (rough)
grep error app.log | cut -c1-16 | uniq -c

Don’t read 50,000 lines. Count, filter, then read the 20 that matter.

Signal vs noise: what to ignore

Noise Why
Health check failures on one pod Probably a bad node, not a systemic issue
connection reset by peer on one request Transient network blip
Deprecation warnings Not your problem at 2am
Anything that was already happening yesterday Check your baseline
Signal Why
Same error repeating hundreds of times per minute Something is systematically broken
Error type you haven’t seen before New failure mode
Error starting at a specific timestamp Correlate with deploy, config change, or upstream outage
Errors on the critical path (auth, payment, checkout) Users are affected now

The 5-minute log triage

  1. Set time window to incident start ± 15 minutes
  2. Filter to ERROR level on the alerted service
  3. Pick the most frequent error message — that’s usually the root symptom
  4. Grab a request ID from one line
  5. Trace that ID across services
  6. Note the timestamp of the first occurrence — correlate with deploys and dashboards

If logs don’t explain it, go to Grafana. If Grafana doesn’t explain it, come back to logs with a narrower hypothesis.

What good looks like

A team with good log hygiene can answer “what broke, when, and for which users” in under five minutes — without reading a novel. Structured fields, request IDs everywhere, and saved searches for the top five failure modes.

You’re not there yet? Start by adding request_id to every log line in your next PR. Future you will be grateful.