Plain text log messages are fast to write but genuinely painful to query at scale — structured logging (consistently formatted, typically JSON) trades a bit of readability for dramatically better searchability.
# Unstructured: readable to a human, but a nightmare to query reliably
logger.info(f"User {user_id} placed order {order_id} for ${total}")
# Structured: a log aggregator can filter, aggregate, and alert on these fields directly
logger.info("order_placed", extra={
"user_id": user_id,
"order_id": order_id,
"total": total,
"currency": "EUR",
})
# Produces: {"event": "order_placed", "user_id": 42, "order_id": 1001, "total": 59.99, ...}
Why This Matters at Scale
With structured logs, “show me all orders over $1000 in the last hour that failed” becomes a genuine, precise query against typed fields — with unstructured text logs, the same question requires fragile regex parsing that breaks the moment someone slightly rewords a log message. This is exactly why every major log aggregation platform (Elasticsearch, Datadog, Splunk) is built around structured field querying, not free-text search alone.
Consistency Is the Hard Part
The real discipline structured logging requires is consistent field naming across the entire codebase — user_id in one service and userId in another breaks cross-service correlation entirely, so agreeing on and enforcing a shared schema is worth doing deliberately rather than letting it emerge organically.