
Grepping Logs Stops Working at About Three Services
Key takeaway: A log line is data, not prose. Structuring it as fields makes aggregation, filtering and correlation possible; leaving it as a sentence makes every question a text-matching exercise.
Where Plain Text Fails
A line reading “failed to process order 8821 for customer 4471 after 3 retries” is perfectly clear to a person reading one file.
Asking how many orders failed for that customer this week requires a regular expression that breaks the moment someone rewords the message. Asking for the p99 duration of failed processing requires parsing a number out of prose. Asking which of those failures correlated with a particular deployment requires joining against information the line does not contain.
Each question is answerable with enough effort, and the effort is spent every time rather than once.
What Structure Provides
The same event as fields:
{"level":"error","event":"order_process_failed","order_id":"8821",
"customer_id":"4471","retries":3,"duration_ms":4210,
"trace_id":"a1b2c3","service":"orders","version":"1.4.2"}
Now the questions are filters rather than expressions. Count by customer, aggregate duration, group by version, and follow the trace identifier into the distributed context.
The event field matters more than it appears. A stable machine-readable event name lets you count occurrences of a specific condition without depending on message wording, which means a copy edit to the human-readable text cannot break a dashboard.
The Field That Makes Distributed Debugging Possible
A trace identifier propagated across every service turns unrelated log lines into a single narrative. Without it, investigating a request that crossed five services means correlating by timestamp and hoping.
Propagation must be end to end. One service that drops the header breaks the chain at that point, and the gap is silent — you get two disconnected halves with no indication they belong together.
Include the deployment version on every line as well. The question “did this start with the last release” is among the most common in incident response, and it is answerable in seconds when the version is a field and painful otherwise.
Keeping Logs Useful and Affordable
| Practice | Effect |
|---|---|
| Log named fields, not whole objects | Prevents accidental secret disclosure |
| Stable event names | Dashboards survive message rewording |
| Trace and span IDs on every line | Makes correlation possible |
| Deployment version on every line | Answers “was it the release” |
| Sample high-volume routine events | Controls cost |
| Never log at debug in production | Volume and secret exposure |
Volume is the practical constraint. Structured logs are larger per line, and the temptation to log everything grows once querying is easy. Sample repetitive successful events aggressively and keep errors unsampled, which is the same principle that governs trace sampling.
Retain long enough to matter. A log platform holding seven days cannot support an investigation into something a customer noticed last month.
Migrating Gradually
Adopting structured logging everywhere at once is not necessary. Start at the boundaries — request entry, external calls, error paths — since those carry most of the diagnostic value, then convert the rest as code is touched.
Emit both formats during transition if tooling requires it, but set a date for removing the unstructured output, because two formats indefinitely is worse than either.
The Bottom Line
Log structured fields with stable event names, propagate a trace identifier through every service, include the deployment version on every line, sample routine events and never sample errors. Start at request boundaries and error paths, where the value concentrates.



