Skip to content

Commit 42499ec

Browse files
committed
docs(notes): add spring traceid documentation for 2025-03-24
1 parent 2da456c commit 42499ec

File tree

1 file changed

+109
-0
lines changed

1 file changed

+109
-0
lines changed

notes/2025-03-24-spring-traceid-en.md

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
---
2+
title: Spring Boot Log4j Request Tracing
3+
lang: en
4+
layout: post
5+
audio: false
6+
translated: false
7+
generated: true
8+
---
9+
10+
In a Java Spring Boot application using Log4j without a built-in trace ID design, tracking a specific log entry back to a frontend-initiated request can indeed be challenging. You're correct that `%t` (thread name) in Log4j can help identify logs tied to a specific thread, but as you noted, this becomes less reliable when requests span multiple threads (e.g., due to async processing, thread pools, or downstream calls). Using a time range to search logs is a workaround, but it’s imprecise and cumbersome, especially in high-traffic systems. Let’s explore some practical advice to improve this situation without overhauling your entire setup.
11+
12+
### Quick and Practical Suggestions
13+
1. **Leverage MDC (Mapped Diagnostic Context) with Log4j**
14+
- Log4j supports MDC, which allows you to attach contextual key-value pairs to logs within a thread (and even propagate them across thread boundaries with some care).
15+
- Generate a unique request ID when the frontend request hits your Spring Boot application (e.g., a UUID), and store it in the MDC. Then, include this ID in your log pattern.
16+
- **How to implement:**
17+
- In a Spring Boot filter or interceptor (e.g., `OncePerRequestFilter`), generate the ID:
18+
```java
19+
import org.slf4j.MDC;
20+
import javax.servlet.FilterChain;
21+
import javax.servlet.http.HttpServletRequest;
22+
import javax.servlet.http.HttpServletResponse;
23+
import java.util.UUID;
24+
25+
public class RequestTracingFilter extends OncePerRequestFilter {
26+
@Override
27+
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) {
28+
try {
29+
String traceId = UUID.randomUUID().toString();
30+
MDC.put("traceId", traceId);
31+
filterChain.doFilter(request, response);
32+
} finally {
33+
MDC.clear(); // Clean up after request
34+
}
35+
}
36+
}
37+
```
38+
- Register the filter in your Spring Boot config:
39+
```java
40+
@Bean
41+
public FilterRegistrationBean<RequestTracingFilter> tracingFilter() {
42+
FilterRegistrationBean<RequestTracingFilter> registrationBean = new FilterRegistrationBean<>();
43+
registrationBean.setFilter(new RequestTracingFilter());
44+
registrationBean.addUrlPatterns("/*");
45+
return registrationBean;
46+
}
47+
```
48+
- Update your Log4j pattern in `log4j.properties` or `log4j.xml` to include the `traceId`:
49+
```properties
50+
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%t] %-5p %c{1} - %m [traceId=%X{traceId}]%n
51+
```
52+
- Now, every log line tied to that request will include the `traceId`, making it easy to trace back to the frontend button click.
53+
54+
2. **Propagate the Trace ID Across Threads**
55+
- If your app uses thread pools or async calls (e.g., `@Async`), the MDC context may not propagate automatically. To handle this:
56+
- Wrap async tasks with a custom executor that copies the MDC context:
57+
```java
58+
import java.util.concurrent.Executor;
59+
import org.springframework.context.annotation.Bean;
60+
import org.springframework.context.annotation.Configuration;
61+
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
62+
63+
@Configuration
64+
public class AsyncConfig {
65+
@Bean(name = "taskExecutor")
66+
public Executor taskExecutor() {
67+
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
68+
executor.setCorePoolSize(10);
69+
executor.setMaxPoolSize(20);
70+
executor.setQueueCapacity(100);
71+
executor.setTaskDecorator(task -> {
72+
Map<String, String> context = MDC.getCopyOfContextMap();
73+
return () -> {
74+
try {
75+
if (context != null) MDC.setContextMap(context);
76+
task.run();
77+
} finally {
78+
MDC.clear();
79+
}
80+
};
81+
});
82+
executor.initialize();
83+
return executor;
84+
}
85+
}
86+
```
87+
- This ensures the `traceId` sticks with the request even across thread boundaries.
88+
89+
3. **Add Frontend Correlation**
90+
- If possible, have the frontend send a custom header (e.g., `X-Request-ID`) with a unique ID when the button is clicked. Your backend can pick this up instead of generating its own UUID. This ties the log directly to the frontend action without extra guesswork.
91+
92+
4. **Fallback: Enhance Logging with Contextual Data**
93+
- If MDC feels like too much overhead right now, enrich your logs with more context at key points (e.g., user ID, session ID, or endpoint name). For example:
94+
```java
95+
logger.info("Button clicked, endpoint=/api/example, user={}", userId);
96+
```
97+
- Combine this with `%t` (thread name) and a tight time range to narrow down logs manually. It’s not ideal but works in a pinch.
98+
99+
### Why Not Just Time Range?
100+
Relying solely on timestamps is fragile because:
101+
- Logs from concurrent requests overlap, especially in a busy system.
102+
- Time precision (e.g., milliseconds) might not be enough to isolate a single request.
103+
- It’s tedious to correlate logs across services or deep call stacks.
104+
105+
### Long-Term Recommendation
106+
If this tracing need grows, consider adopting Spring Boot’s built-in support for distributed tracing (e.g., via Sleuth or Micrometer with Zipkin). Sleuth auto-generates trace and span IDs and integrates seamlessly with Log4j, but it’s more involved to set up. For now, MDC is a lightweight, effective middle ground.
107+
108+
### Final Advice
109+
Start with the MDC approach—it’s simple, leverages Log4j’s native capabilities, and gives you a clear way to tie logs to a frontend-initiated request. You’ll see something like `[traceId=123e4567-e89b-12d3-a456-426614174000]` in every log line for that request, making it trivial to search and trace. Let me know if you need help tweaking this further!

0 commit comments

Comments
 (0)