Structured Logging & Correlating Logs with Metrics¶
What was run¶
A real structured-logging addition to orders_app.py's /checkout endpoint, emitting one real JSON log line per
request with a request ID, the real amount, and the real outcome. Real mixed traffic was sent with periodic
spikes of deliberately oversized amounts, a real metric spike was located in Prometheus's own data, and the real
structured log file was searched for entries falling within that exact window.
Finding the real spike in the metric¶
curl -s ".../api/v1/query_range?query=sum(rate(orders_requests_total{endpoint=\"/checkout\",status!=\"200\"}[30s]))..."
A real, visible rise in the checkout error rate, starting right around timestamp 1793026243. The metric alone
answers "something started failing around this time" — it says nothing about what.
Finding the real log line that explains it¶
@app.post("/checkout")
def checkout(amount_cents: int):
request_id = uuid.uuid4().hex[:10]
...
log_json(request_id=request_id, event="checkout_declined", amount_cents=amount_cents,
status=r.status_code, reason=r.json().get("detail"))
# search the real structured log file for entries right around the spike:
for rec in real_log_entries:
if rec["ts"] >= spike_start - 2 and rec["event"] == "checkout_declined":
print(rec)
A real log line, timestamped 0.4 seconds before the metric's spike even registered (consistent with
Prometheus's 5-second scrape interval catching up to a change that already happened) — naming the exact real
amount, 800000 cents, that caused a real decline. The metric told you when something started going wrong; the
log told you why, down to the specific real request.
How It Actually Works¶
This is the real, concrete payoff of structured logging specifically (JSON fields, not free-text sentences): the
correlation above worked because ts, event, and amount_cents were each queryable, parseable fields, not
words buried in a prose message a human would have to read and interpret. A log line like
"Checkout declined for a large order" would have told a human roughly the same story, but would have been far
harder to filter programmatically for "exactly the entries in this specific time window with this specific
event type" — which is exactly what made this real correlation a precise, automatable lookup rather than a manual
scroll through a log file hoping to spot something relevant.
The real workflow this lesson demonstrates — metric spike, found first, log entries pulled for the matching time window, second — is the standard real incident-response pattern: a dashboard or alert (built in Lesson 2 and Lesson 4) tells you that and roughly when something is wrong, narrowing a potentially huge log volume down to a specific, searchable window, and the structured logs within that window tell you the specific why. Trying to go the other direction — reading every log line first, hoping to notice a pattern — is exactly the approach Lesson 7 showed doesn't scale.
This pattern becomes even more powerful once a real trace_id (built with real cross-service propagation in
Microservices, Level 3, Lesson 8)
is added to the structured fields: a metric spike narrows the time window; the trace ID then lets you follow one
specific failing request's full structured log trail across every service it touched, not just the one that
happened to log the final decline.
Common mistakes¶
- Logging the same real information as unstructured prose instead of structured fields — "Checkout declined,
amount was $8000" can't be filtered or correlated programmatically nearly as reliably as
{"event": "checkout_declined", "amount_cents": 800000}. - Searching logs first, broadly, instead of letting a real metric narrow the time window first — this inverts the efficient real workflow this lesson demonstrated and reintroduces exactly the "scan everything" cost Lesson 7 measured directly.
- Logging a request's outcome without a timestamp precise enough to correlate against metric scrape intervals — this lesson's correlation depended on real sub-second timestamp precision to confidently say the log entry preceded, and plausibly caused, the metric's spike.
Exercise¶
Add a second structured field to the log line, payment_latency_ms, measuring how long the real call to
Payments took. Trigger a real slow-payment scenario (reuse the slow-dependency pattern from
Microservices, Level 3, Lesson 3
if you still have it, or add an artificial delay), and confirm you can correlate a real p95 latency spike in
Prometheus with the real structured log entries whose payment_latency_ms was unusually high during that same
window.