What is structured logging?
The one-paragraph definition
Structured logging is the practice of emitting log entries as machine-readable records (typically JSON) with named fields, rather than as unstructured free text.
Instead of: "User 42 failed to login from 10.0.0.1 at 2026-10-01T14:03:22Z", a structured log entry looks like: {"timestamp": "2026-10-01T14:03:22Z", "level": "warn", "event": "login_failed", "user_id": 42, "ip": "10.0.0.1"}.
The payoff: every field is queryable. Finding "all failed logins for user_id = 42 in the last hour" is a one-field filter, not a brittle regex across logs you cannot change.
Structured logs are the foundation for ad-hoc log analysis, alerting on patterns, correlation with traces and metrics, and compliance reporting.
Why plain-text logs fail at scale
The problems structured logging solves
Parsing tax: every reader (dashboard, SIEM, grep script) has to re-parse plain-text logs. If one service writes "userId=42" and another writes "user_id=42", your filters break.
Ambiguity: "User 42 timed out" — is 42 the user ID or seconds? Was "timed out" the request or the user session? Plain text leaves interpretation to the reader.
Partial information: "Error processing order" with no order ID is useless. Structured logs force you to attach the identifiers you'll need later.
Correlation failure: without a trace_id field, you can't join a log entry to the APM trace or the specific request that produced it.
Alerting pain: "alert when ERROR appears in logs" is noisier than "alert when event=payment_failed AND amount > 1000".
The fields every log entry should include
A minimal production schema
timestamp (ISO 8601 with timezone): "2026-10-01T14:03:22.145Z". Precise to milliseconds.
level: one of DEBUG, INFO, WARN, ERROR, FATAL. Lowercase conventions ("warn") are also fine, just be consistent.
service: the emitting service name. "api", "order-service", "payment-processor".
env: environment. "dev", "staging", "prod".
trace_id (and span_id if available): correlates this log entry to the APM trace of the same request. The single highest-value field.
message: human-readable description. Shorter is better; structured fields carry the detail.
event: a machine-readable action name (snake_case). "login_failed", "order_created", "cache_miss". Enables consistent alerting across services.
Context fields: user_id, tenant_id, order_id, request_id — whatever your domain cares about. Rich context is the whole point of structured logs.
Implementation: Node.js, Python, Go, Java, PHP, .NET
Concrete library choices by language
Node.js: Pino (fast, JSON-native) or Winston (feature-rich). Pino: const pino = require("pino")(); pino.info({user_id: 42, event: "login_failed"}, "login failed").
Python: structlog (purpose-built for structured) or stdlib logging with JSON formatter. structlog: logger.warn("login_failed", user_id=42, ip="10.0.0.1").
Go: Zap (by Uber) or Zerolog. Zerolog: log.Warn().Int("user_id", 42).Str("event", "login_failed").Msg("login failed").
Java: Logback with Logstash encoder, or Log4j2 JSON layout. Add StructuredArguments.keyValue("user_id", 42) to the log call.
PHP: Monolog with JsonFormatter. $logger->warning("login_failed", ["user_id" => 42, "ip" => "10.0.0.1"]).
.NET: Serilog with CompactJsonFormatter. logger.Warning("User {UserId} login failed from {IP}", userId, ip) — Serilog captures the properties as fields.
Common pitfalls
What goes wrong even in experienced teams
Field name inconsistency: user_id vs userId vs user vs uid, all in the same company. Standardize across services via a shared logging library or style guide.
High-cardinality fields in indexes: adding every request's URL as a field explodes index size. Keep high-cardinality values in the message or sampled attributes, not indexed fields.
Logging secrets: API keys, passwords, tokens. Mask at the logger (Pino redact, Winston format.replace()) and audit CI for leaks.
Over-logging: 100 INFO entries per request is wasteful. Think about which entries will actually be useful post-incident.
Not emitting trace_id: structured logs without trace correlation lose most of their value. Hook the logger to your APM / OpenTelemetry context.
From structured logs to insight
What you can do once logs are structured
Ad-hoc queries: "SELECT count(*) FROM logs WHERE event = 'payment_failed' AND env = 'prod' GROUP BY tenant_id ORDER BY 2 DESC". Impossible with plain text.
Alerting on event + context: "alert when event=login_failed AND distinct user_id count > 50 within 1 minute" (brute-force detection).
Dashboards for business metrics derived from logs: successful order rate, average cart value, feature-flag variant distribution.
Correlation with traces: click an error log, see the trace it came from, see the span that failed, see the service metrics for that minute.
SIEM analytics: structured logs power security correlation rules without custom parsers.
Key Takeaways
- Structured logging = JSON (or similar) with named fields instead of free text.
- Every entry should carry: timestamp, level, service, env, trace_id, event, message, and domain context.
- Library choices: Pino/Winston (Node), structlog (Python), Zap/Zerolog (Go), Logback (Java), Monolog (PHP), Serilog (.NET).
- Standardize field names across services; mask secrets at the logger.
- Hook the logger to OpenTelemetry / APM context so every log entry carries trace_id.
- Structured logs enable ad-hoc analytics, pattern-based alerting, trace correlation, and SIEM.