Implement Logback MDC Request ID Tracing in Spring Boot
This guide explains how to integrate Logback with Spring Boot, use MDC to generate and propagate a requestId across services, and implement filters and Feign interceptors for end‑to‑end log traceability in microservice architectures.
Background
In monolithic applications a single log output is often enough, but once the system is split into multiple services and load‑balanced, the call chain becomes complex. In a microservice environment the number of possible request paths grows exponentially, making a unified log‑tracing mechanism essential for debugging.
Spring Boot + Logback Integration
Add the following Maven dependencies to enable Logback and bridge other logging APIs to SLF4J:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jul-to-slf4j</artifactId>
</dependency>Place a logback-spring.xml file under src/main/resources. Configure the console pattern to include a custom requestId stored in MDC:
<property name="CONSOLE_LOG_PATTERN" value="%d{yyyy-MM-dd HH:mm:ss.SSS} %contextName [%thread] %-5level [%X{requestId}] %logger{36} - %msg%n"/>MDC Overview
MDC (Mapped Diagnostic Context) is a thread‑local map provided by SLF4J. It allows each thread to store diagnostic data such as a request identifier, which can later be referenced in log patterns via %X{key}. The core API consists of put, get, remove and clear. A simplified implementation used by Logback is shown below:
public class BasicMDCAdapter implements MDCAdapter {
private InheritableThreadLocal<Map<String, String>> inheritableThreadLocal =
new InheritableThreadLocal<Map<String, String>>() {
@Override
protected Map<String, String> childValue(Map<String, String> parentValue) {
if (parentValue == null) {
return null;
}
return new HashMap<String, String>(parentValue);
}
};
public void put(String key, String val) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
}
Map<String, String> map = inheritableThreadLocal.get();
if (map == null) {
map = new HashMap<>();
inheritableThreadLocal.set(map);
}
map.put(key, val);
}
// get, remove, clear omitted for brevity
}Implementation
Trace‑ID utilities
public class TraceIdUtils {
/** Generate a 32‑character trace ID based on UUID */
public static String getTraceId() {
return UUID.randomUUID().toString().replace("-", "");
}
} public class TraceIdContext {
public static final String TRACE_ID_KEY = "requestId";
public static void setTraceId(String traceId) {
if (StringLocalUtil.isNotEmpty(traceId)) {
MDC.put(TRACE_ID_KEY, traceId);
}
}
public static String getTraceId() {
String id = MDC.get(TRACE_ID_KEY);
return id == null ? "" : id;
}
public static void removeTraceId() {
MDC.remove(TRACE_ID_KEY);
}
public static void clearTraceId() {
MDC.clear();
}
}When a thread pool is used, remember to call clearTraceId() after request processing to avoid leaking data to subsequent tasks.
Servlet filter for request entry
public class TraceIdRequestLoggingFilter extends AbstractRequestLoggingFilter {
@Override
protected void beforeRequest(HttpServletRequest request, String message) {
String requestId = request.getHeader(TraceIdContext.TRACE_ID_KEY);
if (StringLocalUtil.isNotEmpty(requestId)) {
TraceIdContext.setTraceId(requestId);
} else {
TraceIdContext.setTraceId(TraceIdUtils.getTraceId());
}
}
@Override
protected void afterRequest(HttpServletRequest request, String message) {
TraceIdContext.removeTraceId();
}
} @Configuration
public class TraceIdConfig {
@Bean
public TraceIdRequestLoggingFilter traceIdRequestLoggingFilter() {
return new TraceIdRequestLoggingFilter();
}
}Feign interceptor for downstream calls (Spring Cloud OpenFeign)
@Configuration
public class FeignConfig implements RequestInterceptor {
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header(TraceIdContext.TRACE_ID_KEY, TraceIdContext.getTraceId());
}
}Verification
After the above setup, a controller request produces logs that contain the same requestId across services, e.g.:
2021-04-13 10:58:31.092 cloud-service-consumer-demo [http-nio-7199-exec-1] INFO [ef76526ca96242bc8e646cdef3ab31e6] c.b.demo.controller.CityController - getCity
2021-04-13 10:58:31.185 cloud-service-consumer-demo [http-nio-7199-exec-1] WARN [ef76526ca96242bc8e646cdef3ab31e6] o.s.c.o.l.FeignBlockingLoadBalancerClient - ...The identical requestId allows you to correlate all log entries belonging to the same request across the entire call chain.
Conclusion
When a request reaches the first service, the filter checks for an existing requestId. If absent, a new ID is generated and stored in MDC. Subsequent HTTP calls propagate the ID via the requestId header (Feign interceptor or manual header addition). Each service’s Logback configuration prints the ID, achieving end‑to‑end log correlation without external tracing frameworks.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Senior Brother's Insights
A public account focused on workplace, career growth, team management, and self-improvement. The author is the writer of books including 'SpringBoot Technology Insider' and 'Drools 8 Rule Engine: Core Technology and Practice'.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
