skip to content

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