On this page
Structured logging vs unstructured
The shift from human-readable text logs to machine-parsable JSON logs to enable complex querying and automated alerting.
Reference Card
- Origin/Prior Art: Shift to microservices and centralized log search (Splunk, ELK).
- Related Practices: JSON logging, Log aggregation, Trace ID injection.
- Key Concepts: Key-value pairs, Machine-parsability, Schema evolution.
- Primary Failure Modes: Inconsistent keys, Log volume explosion, Regex parsing failures.
Introduction
Historically, developers wrote logs designed to be read by human eyes staring at a terminal screen. These unstructured logs were simple sentences: User Rantideb failed to login from IP 192.168.1.5 at 14:02.
While easy for a human to read, these string-based logs are incredibly difficult for machines to analyze. If an operator wants to alert on “more than 50 failed logins from a single IP,” the logging system must run complex, fragile Regular Expressions against millions of strings to extract the IP address.
Structured logging solves this by emitting logs as strictly formatted data objects, almost universally encoded as JSON. The same event becomes {"event": "login_failed", "user": "Rantideb", "ip": "192.168.1.5", "timestamp": "2026-07-31T14:02:00Z"}.
Mental Model
Think of unstructured logs as a giant box of handwritten receipts. If you want to know how much you spent on coffee last month, you have to read every single receipt, find the word “coffee,” and manually tally the numbers.
Think of structured logs as an Excel spreadsheet. Every event is a row, and every property (IP address, user ID, error code) is a distinct column. If you want to know how much you spent on coffee, you simply apply a filter to the “Category” column and sum the “Amount” column. It is mathematically precise and computationally trivial.
Operational Pattern
To implement structured logging, applications must entirely abandon language-default print statements (like console.log() or System.out.println()).
- Standardized Library: Teams adopt a structured logging library (like Pino for Node.js, Logrus for Go, or Serilog for .NET).
- Context Injection: The library is configured to automatically inject global context into every log event, such as the current
trace_id, theenvironment(prod/staging), and thepod_name. - Structured Emission: Instead of writing
logger.info(f"Order {order_id} processed in {ms}ms"), the developer writeslogger.info("Order processed", {"order_id": order_id, "duration_ms": ms}). - Machine Ingestion: The application writes the resulting JSON string to
STDOUT. The container orchestrator captures it and forwards it to a centralized database (like Elasticsearch), which automatically indexesorder_idandduration_msas searchable fields.
Failure Modes
Schema Drift and Inconsistent Keys
If Team A logs user identifiers as {"user_id": 123}, and Team B logs them as {"userId": "123"}, the centralized logging system cannot easily correlate them. Furthermore, if one team sends a numeric value for a key, and another sends a string for the same key, strictly typed indexers (like Elasticsearch) will reject the conflicting logs, permanently losing data. Organizations must enforce a strict, centralized schema for common log keys.
The “Message” Dumping Ground
Developers migrating from unstructured logging often just dump their entire unstructured string into a JSON msg field: {"msg": "User Rantideb failed to login from 192.168.1.5", "level": "error"}. This provides absolutely zero benefit. The data must be extracted into distinct key-value pairs to be useful.
Log Volume Explosion
JSON is verbose. Wrapping a 30-character log message in 200 characters of JSON keys, timestamps, and context metadata drastically increases the sheer volume of bytes written to disk and sent over the network. This can choke I/O limits on heavily loaded nodes and drastically increase log retention costs if the logs are not aggressively filtered or sampled.
Security
Structured logging makes it significantly easier to redact sensitive information. With unstructured logs, redacting a credit card number requires running a regex over every string. With structured logs, a log forwarding agent can simply be configured: “If the JSON payload contains a key named credit_card, drop the key or hash the value before forwarding.” This deterministic redaction prevents PII leaks.
Operational Guidance
Never write multiline logs. A Java stack trace dumped as 50 separate lines of text will be treated as 50 unrelated events by the logging system. The entire stack trace must be escaped and embedded into a single JSON field (e.g., {"error_trace": "NullPointerException\n\tat com.app.Main..."}).
Diagnostics
To test if your logs are properly structured, attempt to pipe them into the jq command-line tool.
kubectl logs my-app | jq '.'
If jq formats and colorizes the output perfectly, your application is emitting valid JSON. If it throws parsing errors, your application is likely mixing unstructured startup text with structured logs, which will break downstream aggregators.
Related topics
Sources & further reading
- The 12-Factor App: Logs - 12factor.net
- Structured Logging - Honeycomb