Implement End-to-End Log Traceability in Spring Cloud Using MDC
This guide explains how to add a traceId to logs in a Spring Cloud microservice architecture by using MDC, configuring Logback, creating interceptors, and propagating the trace across services so that logs can be correlated and visualized via ELK Stack.
Introduction
We are building a basic project framework on top of Spring Cloud microservices. Existing components such as Swagger and Logback serve as the foundation (level 0.5), and we are assembling additional modules (level 1.5) to complete the framework.
Why Build Our Own Framework?
Our team cannot use third‑party frameworks like Ruoyi, and our specific requirements and technology stack favor a custom solution.
Core Features
Multiple microservice modules with a demo module for extension
Core framework module extraction
Eureka service registry
OpenFeign remote calls
Logback with traceId support
Swagger API documentation
Shared configuration files
ELK log search
Custom starter (planned)
Redis cache with Sentinel high availability
MySQL high availability
MyBatis‑Plus integration
Link tracing component (planned)
Monitoring (planned)
Utility classes (planned)
Gateway (technology to be decided)
Audit logs to ES (planned)
Distributed file system (planned)
Scheduled tasks (planned)
Pain Points
Pain Point 1: In‑process log correlation
A single request may invoke many methods and produce several log entries that cannot be linked together.
Example: Method A logs entries 1 and 5, B logs entry 2, C logs entries 3 and 4. The logs share only timestamps, making correlation impossible under concurrent requests.
Pain Point 2: Cross‑service log correlation
Each microservice writes its own logs; how can logs from different processes be linked?
Example: Order service (method A) calls Coupon service (method D). Both services write separate log files, producing ten unrelated entries.
Pain Point 3: Cross‑thread log correlation
How to associate logs from a main thread and its child threads?
Example: Main thread method A spawns a child thread executing method E. Logs from A and E appear unrelated.
Pain Point 4: Third‑party calls
Currently not addressed; future work will consider tracing external calls.
Solution Overview
1.1 Chosen Approach
We evaluate three options: SkyWalking traceId, Elastic APM traceId, and an MDC‑based custom traceId. To keep the initial setup lightweight, we adopt the MDC solution.
1.2 MDC (Mapped Diagnostic Context)
MDC stores thread‑local context data. When using Log4j or Logback, each thread can have its own MDC, allowing any code in that thread to retrieve values such as a traceId.
Implementation Details
2.1 Correlating Logs Within a Single Request
Steps:
Add %X{traceId} to the Logback pattern in logback.xml.
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %X{traceId} %-5level %logger - %msg%n</pattern>Create an interceptor that extracts traceId from the incoming HTTP header; if absent, generate a UUID and store it in MDC.
@Service
public class LogInterceptor extends HandlerInterceptorAdapter {
private static final String TRACE_ID = "traceId";
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
String traceId = request.getHeader(TRACE_ID);
if (StringUtils.isEmpty(traceId)) {
MDC.put("traceId", UUID.randomUUID().toString());
} else {
MDC.put(TRACE_ID, traceId);
}
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
MDC.remove("traceId"); // prevent memory leak
}
}Register the interceptor in a WebMvcConfigurer implementation.
@Configuration
public class InterceptorConfig implements WebMvcConfigurer {
@Resource
private LogInterceptor logInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(logInterceptor).addPathPatterns("/**");
}
}After this setup, every log entry automatically includes the same traceId, enabling correlation across multiple log lines.
2.2 Cross‑service Log Correlation
We add a Feign interceptor that injects the traceId into the outgoing request header, ensuring the downstream service receives the same identifier.
@Configuration
public class FeignInterceptor implements RequestInterceptor {
private static final String TRACE_ID = "traceId";
@Override
public void apply(RequestTemplate requestTemplate) {
requestTemplate.header(TRACE_ID, (String) MDC.get(TRACE_ID));
}
}Both services now log with identical traceId values, which can be searched in Elasticsearch via Kibana to reconstruct the full call chain.
Conclusion
By using an interceptor and MDC, we inject a traceId into every log entry, enabling end‑to‑end tracing of method calls within a process and across microservices. Importing the logs into an ELK Stack and searching by traceId provides a complete view of the request flow.
Future work includes handling cross‑thread tracing, third‑party calls, and adding monitoring components.
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.
ITPUB
Official ITPUB account sharing technical insights, community news, and exciting events.
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.
