skip to content

Correlation IDs & Trace-ID in Logs

Correlation puts the trace and span id into the MDC so every log line can be tied back to one request across services. Interviewers ask how you find all the logs for a single failed request, and this is the answer.

part ofSpring Frameworkoverview, primer and where to startread it →
on this pageshow

questions

5

How does Spring Boot get traceId and spanId into log lines by default? Explain the pattern, MDC, and what enables it.

level: middleimportance: must knowfreq 65%

answer

  1. MDC = thread-local map, %X{traceId:-}
  2. Micrometer Tracing scope-decorator writes traceId/spanId to MDC
  3. logging.pattern.correlation property, not a %correlationId converter
  4. bridge-brave or bridge-otel enables it
  5. [app,traceId,spanId] auto-prepended

basics

~20 s

Add Micrometer Tracing to the classpath. When a span is active it copies traceId and spanId into SLF4J's MDC (a thread-local map). Spring Boot's default log pattern includes a correlation part that prints those MDC values as [app,traceId,spanId].

solid answer

~40 s

Two pieces work together. First, **MDC** (Mapped Diagnostic Context) is a thread-local key/value map in SLF4J/Logback; the pattern converter `%X{traceId:-}` prints the value under key `traceId`, or `-` if missing. Second, **Micrometer Tracing** (with a bridge like `micrometer-tracing-bridge-brave` or `-bridge-otel`) is what *puts* traceId/spanId into MDC whenever a span becomes the current scope, via its correlation/scope-decorator machinery. Spring Boot auto-configures this and, on detecting a tracer, sets the `logging.pattern.correlation` property so the default console/file pattern prepends `[${spring.application.name:},%X{traceId:-},%X{spanId:-}]`. So you get correlated logs with zero code — just the actuator + a tracing bridge dependency. Note there is no magic `%correlationId` converter; it's plain MDC (`%X`) driven by the property.

code

yaml · 9 lines
yaml
# application.yml — the correlation part is set for you, but you can override it:
logging:
  pattern:
    correlation: "[${spring.application.name:},%X{traceId:-},%X{spanId:-}] "

management:
  tracing:
    sampling:
      probability: 1.0   # export every trace (dev); IDs still stamp in logs regardless

go deeper

for a junior

Knows adding a dependency makes IDs appear in logs.

for a middle

Can explain MDC + %X converter + the correlation pattern property + which dependency enables it.

for a senior

Understands scope-based MDC population and the sampling-vs-stamping distinction.

for a principal

Can reason about structured/JSON logging, custom bridges, and keeping correlation intact through custom appenders.

## The two collaborating layers ### 1. MDC — the storage **MDC (Mapped Diagnostic Context)** is a per-thread `Map<String,String>` provided by SLF4J and implemented by Logback. You (or a library) call `MDC.put("traceId", "...")`, and any log statement on that thread can print it. In a Logback pattern, the converter is `%X{key}` or, with a default, `%X{key:-fallback}`: ``` %X{traceId:-} -> prints the traceId, or empty if not set ``` MDC is **thread-local**: values set on thread T are only visible while logging on thread T. ### 2. Micrometer Tracing — the writer Micrometer Tracing (Spring Boot 3's replacement for Spring Cloud Sleuth) creates spans and, crucially, has a **correlation** feature that mirrors the current span's `traceId` and `spanId` into MDC whenever that span is *scoped* (made current). This is done by scope decorators / event listeners wired by the bridge you choose: - **Brave bridge** (`micrometer-tracing-bridge-brave`) — uses Brave's `MDCScopeDecorator`-style mechanism. - **OpenTelemetry bridge** (`micrometer-tracing-bridge-otel`) — uses an SLF4J event/context bridge. Spring Boot auto-configures these under `MicrometerTracingAutoConfiguration` and the relevant Brave/OTel autoconfigurations. When the current span opens, MDC gets `traceId`/`spanId`; when it closes, they're cleared. ## What Spring Boot sets for you When a `Tracer` bean is present, Spring Boot defines the **correlation** portion of the log pattern via the property `logging.pattern.correlation` (internally referenced as `LOG_CORRELATION_PATTERN`). Its default value is effectively: ``` [${spring.application.name:},%X{traceId:-},%X{spanId:-}] ``` The stock console pattern (`CONSOLE_LOG_PATTERN`) includes `${LOG_CORRELATION_PATTERN}` right before the logger name, which is why a line reads: ``` ... INFO [orders,af0e...,9b21...] c.g.OrderController : fetching order 42 ``` ## Common misconception to correct There is **no built-in `%correlationId` conversion word** in stock Logback/Spring Boot. Correlation is achieved with plain MDC (`%X{traceId:-}` / `%X{spanId:-}`) plus the `logging.pattern.correlation` property. Anyone claiming a `%correlationId` converter is conflating the *feature name* with the actual pattern mechanism. ## Enabling / disabling / customizing - **Enable**: add `spring-boot-starter-actuator` + one `micrometer-tracing-bridge-*`. Sampling defaults are low in some setups (`management.tracing.sampling.probability`), but MDC stamping of logs happens regardless of whether the span is *exported* — the IDs still exist and print. - **Customize the printed part**: override `logging.pattern.correlation` in `application.yml`. - **Custom appender/JSON logging**: if you replace the pattern, you must re-include the correlation part yourself, or (Spring Boot 3.4+) use structured logging which includes MDC keys. ## Gotcha Because the writer is scope-based and MDC is thread-local, logs emitted *outside* an active span (startup, background jobs with no span, work moved to another thread) show empty `-` for traceId/spanId. That's expected — there is genuinely no current trace there.

  • Is there a Logback converter called %correlationId?
    No. Correlation uses the standard MDC converter %X{traceId:-} and %X{spanId:-}, wired through the logging.pattern.correlation property. There is no stock %correlationId conversion word.
  • If sampling probability is 0.1, will most log lines still show a traceId?
    Yes. Sampling controls whether the trace is exported to the backend, not whether IDs are created and stamped into MDC. A span (and its IDs) still exists locally, so logs still print traceId/spanId.

saying these in an interview costs you the question

  • Claiming Spring Boot ships a %correlationId Logback converter
  • Thinking you must call MDC.put manually for traceId/spanId — the tracing bridge does it
  • Believing low sampling probability removes traceId from logs

context

open as a page

Why do traceId/spanId disappear from logs on @Async, thread pools, or reactive chains, and how do you fix it?

level: seniorimportance: must knowfreq 60%

basics

~20 s

traceId/spanId live in MDC, which is thread-local. When work moves to another thread (executor, @Async, reactor), the new thread has no MDC, so logs show empty IDs. Fix it by propagating context: wrap executors, use a TaskDecorator, or enable Reactor context propagation.

open as a page

What is a correlation ID (trace ID) in application logs, and why is it useful?

level: juniorimportance: should knowfreq 55%

basics

~20 s

A correlation ID (trace ID) is a unique identifier attached to every log line produced while handling one request. It lets you filter logs and see everything that happened for that single request, even across services.

open as a page

How is the same traceId carried across service boundaries so logs from different services correlate, and how do those logs tie back to a trace?

level: seniorimportance: should knowfreq 48%

basics

~20 s

The traceId is sent to downstream services in an HTTP header (by default the W3C traceparent header). The downstream service extracts it, continues the same trace, and stamps the same traceId into its own logs. Because logs and the tracing backend share that traceId, you can jump between them.

open as a page

How would you add a custom field (e.g. userId or tenantId) to every log line AND propagate it downstream alongside traceId? Contrast MDC vs baggage.

level: principalimportance: nice to knowfreq 30%

basics

~20 s

Plain MDC.put adds a field to local logs only and doesn't cross services. Use Micrometer Tracing baggage: create a baggage field, and configure it to be written into MDC and propagated in headers. Then userId/tenantId appears in every service's logs, not just the first.

open as a page