A log line like User failed to check out is almost useless in production. Which user? Which cart? Which request? Which release? Structured logging — emitting logs as machine-readable JSON with consistent fields instead of free-form strings — is what makes logs queryable, correlatable, and actually useful when something breaks at 2 a.m. This guide covers the fields that matter, how to correlate logs with errors and traces, and the mistakes that quietly ruin a logging setup.
Why structured logs beat plain strings
Plain-text logs are written for humans reading one line at a time. Production doesn't work that way — you're searching millions of lines for the ten that explain an incident. Structured logs are written for machines to filter and aggregate:
{
"level": "error",
"msg": "checkout failed",
"timestamp": "2026-04-02T14:03:11Z",
"user_id": "u_8471",
"order_id": "o_5567",
"request_id": "req_a1b2c3",
"release": "web@2.4.0",
"duration_ms": 812
}
Now you can answer "show every checkout failure for release 2.4.0 with duration over 500ms" as a query, not a grep. That's the whole point: logs you can slice by field.
The fields that matter
Every log line should carry a consistent core so entries can be joined across services:
- timestamp (ISO 8601, UTC) and level (
debug/info/warn/error). - message — a short, stable string; put the variable data in fields, not the message.
- request_id / trace_id — the thread that ties one request's logs together.
- user_id, release, environment — the who, what version, and where.
Keep the message field constant and push the variables into their own keys. "checkout failed" with a user_id field groups cleanly; "checkout failed for u_8471" creates a new unique message per user and destroys aggregation — the same reason error trackers rely on fingerprinting.
Correlate logs with traces and errors
A request_id (or trace_id) is the single most valuable field you can add. Attach it once at the edge and thread it through every log line and downstream call, and you can reconstruct a whole request across services. This is the same identifier that powers distributed tracing — the difference between a trace id and a span id is exactly what lets you pivot from a log line to the full span waterfall.
The three signals reinforce each other: logs, metrics, and traces each answer a different question, and a shared correlation id is the seam that stitches them into one story. When an error tracker captures an exception, including that same id on the event lets you jump straight from the grouped issue to the exact logs around it.
Log levels, sampling, and cost
Not everything deserves to be an error. Reserve error for things a human should act on, warn for recoverable surprises, info for business events, and debug for development. If error means "someone gets paged," don't cry wolf — the same discipline that avoids alert fatigue applies to log levels.
High-volume paths need sampling: log 1 in N successful requests but 100% of failures. That keeps cost sane while preserving the signal you actually need — you never want to sample away the failures.
Mistakes to avoid
Never log secrets or PII — passwords, tokens, full card numbers, or raw request bodies. Structured logs are easy to ship and easy to leak. Redact at the source, the same discipline as scrubbing sensitive data from error reports.
Other common traps: inconsistent field names across services (userId vs user_id vs uid — pick one), logging inside tight loops, giant nested objects that blow up your ingestion bill, and using logs as your alerting system. Logs tell you what happened, step by step; they're not a substitute for error tracking, which tells you what broke and for whom.
Getting started
Pick a logging library that emits JSON (pino, structlog, zap, logback-json), define a small shared schema of required fields, inject a request_id at your entry point, and make sure your error events carry the same id. Do that and your logs stop being a wall of text and become a database you can ask questions of.
Start tracking errors in minutes
Correlate your structured logs with grouped errors and traces. Point the Sentry SDK at LightTrace and pivot from an exception to its full request context in one click.
Structured logging isn't about logging more — it's about logging in a shape you can query. Consistent fields, a correlation id, sane levels, and no secrets: that's the whole recipe, and it pays off the first time you have to debug something you can't reproduce.