Structured JSON Logging: A Practical Guide for Developers
Logging is a critical part of observability in modern distributed systems. Unlike metrics and traces, logs offer the most flexibility for analyzing application events. This guide provides concrete patterns for implementing effective logging using structured JSON format, MDC context, and clear error level separation.
Why Structured Logs Beat Plain Text
Traditional plain-text logs are easy to read in the console but create major headaches for automated processing. Parsing them requires complex regex patterns, extracting specific data is tricky, and changing the format is a nightmare. In distributed systems where incidents demand quick analysis across multiple sources, these issues become deal-breakers.
Structured JSON logs solve these problems:
- Simplify searching and filtering by specific fields
- Enable partial indexing in search systems
- Make post-incident data extraction automatic
- Ease personal data filtering and metadata addition
Example of an ineffective plain-text log:
2025-03-15 10:16:01.795 INFO 12345 --- [nio-8080-exec-1] c.e.UserController : User saved to DB: User{id=123, [email protected], role=USER}
Example of a structured JSON log:
{
"timestamp": "2025-03-22T10:15:30.123Z",
"level": "ERROR",
"service": "auth-service",
"traceId": "1fd7a997-6517-4b63-9baa-4238f8012734",
"entrypoint": "HTTP request POST /api/user/auth",
"message": "Failed to authenticate user",
"user_id": "user-789",
"error": {
"type": "InvalidCredentials",
"details": "Provided password does not match"
}
}
Mapped Diagnostic Context for End-to-End Tracing
Mapped Diagnostic Context (MDC) in SLF4J solves the challenge of passing contextual info through call chains. Instead of manually threading IDs through every method, MDC ties data to the current execution thread.
Key MDC usage aspects:
- Business identifiers — user_id, order_id, transaction_id
- Technical context — traceId, spanId for tracing
- Entrypoint metadata — entrypoint type, retry attempt number
- Auto cleanup — always clear context after request handling
MDC shines at entrypoints, where business IDs need to be available from the start—often pulled from headers before reading the payload.
Entrypoints and Their Logging
An entrypoint (EP) is the abstraction for starting business logic execution. Common types in modern apps:
- HTTP/REST servers
- gRPC servers
- Kafka consumers
- Cron jobs and scheduled tasks
- Message queue listeners
Every EP should follow a consistent logging schema:
- Inputs — request metadata and payload
- Execution context — business IDs, technical params
- Outputs — processing result or error
- Status — success/failure accounting for retries
Separating Errors and Warnings in Distributed Systems
Retries are standard for reliability in distributed systems, directly shaping logging strategy:
Use WARN level for:
- Errors on intermediate retry attempts
- Transient failures fixable by retry
- Issues not needing immediate operator attention
Reserve ERROR level for:
- Final failed attempts
- Critical errors making further retries pointless
- Situations requiring immediate intervention
Number retries from high to low (e.g., 2 → 1 → 0), where 0 is the last try. This makes filtering final errors straightforward.
Practical Implementation with Interceptors
Modern frameworks offer interceptors for cross-cutting concerns. Entrypoint logging flow via interceptors:
- Context init — set traceId, spanId in MDC
- Log entry — capture metadata and inputs
- Process request — run business logic with MDC auto-propagation
- Log exit — record result or error
- Clean context — clear all MDC data
Key implementation pieces:
- Entry/exit logging interceptors per EP type
- Tracing integration (Jaeger, Zipkin)
- Auto-extract business IDs from headers
- Consistent JSON log serialization
Mandatory Fields in Structured Logs
For consistency and easy analysis, every log must include:
- Core technical fields
- timestamp in ISO-8601 format
- log level (ERROR, WARN, INFO, DEBUG)
- service name and version
- app instance ID
- Execution context
- traceId and spanId for request correlation
- requestId
- entrypoint type and name
- retry count
- Business context
- key business IDs (user_id, order_id, etc.)
- request metadata (HTTP method, endpoint, consumer group)
- environment (stage, region, cluster)
- Event data
- human-readable message
- structured event details
- error stacktrace (for ERROR)
- duration (for EP logs)
Integrating with the Observability Ecosystem
Structured logs aren't standalone—they're part of full observability:
Metrics correlation:
- Shared IDs between logs and metrics
- Link error logs to error rate metrics
- Use logs to contextualize metric anomalies
Tracing integration:
- Unified traceId/spanId across logs and traces
- Enrich traces with log details
- Debug specific spans via logs
Automated processing:
- Alerts based on log patterns
- Incident management system hooks
- Auto-extract data for recovery
Key Takeaways
- JSON structured logs deliver machine-readable format for seamless automated processing and analysis.
- MDC context propagates business IDs through call chains without method signature changes.
- Clear error/warn separation supports retry logic and cuts monitoring noise.
- Unified EP logging schema ensures consistency for distributed transaction analysis.
- Standard field set enables event correlation and efficient search.
— Editorial Team
No comments yet.