The Old Way: Unstructured (Text for Humans)
Traditionally, developers wrote logs as free-form sentences meant for human eyes.
[2023-10-27 10:00:01] INFO User 'bob' checked out cart with 5 items totaling $150.00.
[2023-10-27 10:00:02] ERROR Payment gateway timeout after 30s for user 'alice'.
[2023-10-27 10:00:03] WARN Inventory low: only 2 units of SKU-9921 remaining.
These are pleasant to read in a terminal. But they are a nightmare for machines. If you want to graph the average cart total over time, you must write a brittle regular expression to extract $150.00 and convert it to a float. If a developer changes the message from "totaling" to "worth," your regex silently breaks and your graph flatlines. Unstructured logs are optimized for the author, not the operator.
The New Way: Structured (JSON for Machines)
Structured logging means emitting events as machine-readable objects — almost always JSON — where every piece of data is an explicit, typed field.
{
"timestamp": "2023-10-27T10:00:01Z",
"level": "INFO",
"service": "checkout-service",
"message": "User checked out cart",
"user_id": "bob",
"items_count": 5,
"total_amount": 150.00,
"currency": "USD",
"cart_id": "cart-7f3a9b"
}
Now the machine doesn't need to guess. total_amount is always a number. user_id is always a string. You can filter by user_id="bob" with zero ambiguity, or calculate the P95 of total_amount across millions of events in seconds. Adding a new field like cart_id doesn't break any existing queries — it just enriches future ones.
Choosing a Structured Logging Library
Most modern languages have first-class structured logging support:
| Language | Library |
|---|---|
| Go | log/slog (stdlib, Go 1.21+), zap, zerolog |
| Python | structlog, python-json-logger |
| Java | Logback with logstash-logback-encoder |
| Node.js | pino, winston |
A minimal example using Go's standard-library log/slog: logging an info-level message with a set of key-value pairs directly, rather than interpolating them into a sentence.
This emits a JSON line automatically. The developer writes readable code; the operator gets queryable data.
Key architectural point: Structured logging is not just a formatting choice — it's a contract between your application and your observability platform. Once you commit to it, you can build dashboards, alerts, and automated runbooks on top of specific field values. Breaking that contract (e.g., changing user_id to userId) silently breaks downstream systems. Treat your log schema with the same discipline you'd apply to an API schema.