Unravel โบ Java concepts โบ Cloud and operations
Structured logging and correlation ids (MDC)
In a busy service, plain log lines from different requests are mixed together.
Learn these first
At a glance
Writing the ticket number on every note about a support case.
- 1MDC.put("requestId", id) stores a value in a thread-local mapThe log pattern or JSON encoder prints it on every line written by that thread: %X{requestId}.
- 2A servlet filter reads or generates X-Request-Id at the start of each request, puts it in the MDC, adds it to the response and (in a finally) clears itOutgoing calls forward the same header so other services log the same id.
- 3Structured (JSON) logging with Logstash encoder or Spring Boot 3.4 built-in support emits fields (level, logger, traceId, message) that a log platform can index and query instead of regex-parsing text
- 4Key-value API: log.atInfo().addKeyValue("orderId", id).log("order paid") adds data as fields rather than formatting it into the message
- The MDC is thread-local, so it vanishes in @Async, executors, CompletableFuture and reactive pipelines
- Always clear the MDC in a finally block
- Logback and Log4j2 keep the MDC in a ThreadLocal<Map<String,String>> (or an inheritable variant)
String requestId = Optional.ofNullable(request.getHeader("X-Request-Id")).orElse(UUID.randomUUID().toString());MDC.put("requestId", requestId);try { log.info("Handling checkout"); chain.doFilter(request, response);} finally { MDC.clear();}- line 2 uses put, the core of Structured logging and correlation ids (MDC)
- line 7 uses clear, the core of Structured logging and correlation ids (MDC)
Read the full explanation (about 2 min)
In a busy service, plain log lines from different requests are mixed together. Putting a correlation id and other fields on every line (the MDC) and writing logs as JSON lets you filter one request out of millions.
A mental model
Writing the ticket number on every note about a support case. Without it, notes from fifty customers are in one pile; with it you pull out one case in a second.
How it works
MDC.put("requestId", id) stores a value in a thread-local map. The log pattern or JSON encoder prints it on every line written by that thread: %X{requestId}.
A servlet filter reads or generates X-Request-Id at the start of each request, puts it in the MDC, adds it to the response and (in a finally) clears it. Outgoing calls forward the same header so other services log the same id.
Structured (JSON) logging with Logstash encoder or Spring Boot 3.4 built-in support emits fields (level, logger, traceId, message) that a log platform can index and query instead of regex-parsing text.
Key-value API: log.atInfo().addKeyValue("orderId", id).log("order paid") adds data as fields rather than formatting it into the message.
Going deeper
The MDC is thread-local, so it vanishes in @Async, executors, CompletableFuture and reactive pipelines. Copy it with a task decorator (MDC.getCopyOfContextMap()) when you hand work to another thread.
Always clear the MDC in a finally block. Pooled threads are reused, and a stale id from the last request makes logs lie.
Never log personal data, tokens or full payloads. Mask them, and log ids so the data can be looked up under access control.
Under the hood
Logback and Log4j2 keep the MDC in a ThreadLocal<Map<String,String>> (or an inheritable variant). When a log event is created, it snapshots the map into the event, so encoders on another thread (async appenders) still see the right values.
Common mistakes
- MDC not cleared after the request.
- Expecting MDC values in async threads.
- Formatting data into the message instead of fields.
- Logging secrets and personal data.
How Unravel recognises it
When you paste code, Unravel looks for these signs of structured logging and correlation ids (mdc). Spotting them yourself is a good habit when you read unfamiliar code:
- Types:
StructuredArgumentsLogstashEncoderThreadContextLoggingEventBuilderJsonLayout - Method calls:
MDC.put()MDC.remove()MDC.clear()MDC.putCloseable()addKeyValue()atInfo()atError()StructuredArguments.kv() - Configuration keys:
traceIdcorrelationIdX-Correlation-IdX-Request-IdrequestId
Part of a learning path
- Cloud-native Java ยท previous: Distributed tracing (OpenTelemetry) ยท next: Service discovery and central config (Spring Cloud)
Related concepts
Frequently asked questions
What is Structured logging and correlation ids (MDC)?
In a busy service, plain log lines from different requests are mixed together. Putting a correlation id and other fields on every line (the MDC) and writing logs as JSON lets you filter one request out of millions.
What are the most common mistakes with Structured logging and correlation ids (MDC)?
MDC not cleared after the request. Expecting MDC values in async threads. Formatting data into the message instead of fields. Logging secrets and personal data.
How can I see Structured logging and correlation ids (MDC) in real code?
Paste your own Java into Unravel and it will point out where structured logging and correlation ids (mdc) appears, explain how it works at the detail level you choose, and draw the flow of the code around it.