Master Request Tracing and Log Filtering in Spring Boot 3 with MDC and TurboFilter
This tutorial walks through implementing request tracing using MDC, configuring Logback's TurboFilter for dynamic log filtering, and creating custom filters in Spring Boot 3, complete with code examples, configuration snippets, and practical considerations for performance and correctness.
Introduction
In distributed systems, logs are essential for monitoring and debugging. When log volume grows, precise filtering and efficient processing become critical. This article explores two key techniques for log management in Spring Boot 3: request tracing with MDC and early‑stage log filtering using Logback's TurboFilter.
2.1 Request Tracing
What is MDC? Mapped Diagnostic Context (MDC) is a thread‑local storage for key‑value pairs that can be automatically included in log messages, enabling correlation of logs across a request flow.
To attach a unique identifier to each request, a servlet Filter is defined that extracts a x-trace header (or generates a UUID) and puts it into MDC under the key traceXId. The filter also logs the request path and ensures the MDC entry is removed after the request completes.
@Component
public class TraceXFilter implements Filter {
private static final Logger logger = LoggerFactory.getLogger(TraceXFilter.class);
public static final String TRACE_KEY = "traceXId";
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
String traceId = req.getHeader("x-trace");
if (!StringUtils.hasLength(traceId)) {
traceId = UUID.randomUUID().toString().replace("-", "").toUpperCase();
}
MDC.put(TRACE_KEY, traceId);
logger.info("请求: {}", req.getServletPath());
try {
chain.doFilter(request, response);
} finally {
MDC.remove(TRACE_KEY);
}
}
}The Logback configuration references the MDC key to include the trace ID in each log line:
<configuration scan="true">
<contextName>trace-mdc</contextName>
<property name="TRACEX_PATTERN" value="%green(%d{HH:mm:ss}) traceId:【%red(%X{traceXId})】 %highlight(%-5level) [%yellow(%thread)] %logger Line:%-3L - %msg%n"/>
<appender name="TRACEX" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>${TRACEX_PATTERN}</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
<springProfile name="dev | default">
<root level="INFO">
<appender-ref ref="TRACEX"/>
</root>
</springProfile>
</configuration>A simple controller demonstrates the effect:
private static final Logger logger = LoggerFactory.getLogger(ApiController.class);
@GetMapping("/query")
public ResponseEntity<?> query() {
logger.info("query start...");
return ResponseEntity.ok("api query...");
}When the endpoint is called, the console output shows the unique traceXId attached to each log entry, making it easy to follow the request path.
2.2 Log Filtering with TurboFilter
Logback’s TurboFilter runs at the very beginning of the logging pipeline, even before level checks, allowing ultra‑fast decisions about whether a log event should be processed.
Typical use‑cases include dynamic filtering based on session data, avoiding log pollution, and early discarding of messages that do not meet custom criteria.
Example configuration that only accepts logs when the MDC key userId equals admin:
<configuration>
<turboFilter class="ch.qos.logback.classic.turbo.MDCFilter">
<MDCKey>userId</MDCKey>
<Value>admin</Value>
<OnMatch>ACCEPT</OnMatch>
<OnMismatch>DENY</OnMismatch>
</turboFilter>
</configuration>By modifying TraceXFilter to store userId from a request parameter, only requests with name=admin produce log output, while others are silently dropped.
public class TraceXFilter implements Filter {
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
MDC.put("userId", request.getParameter("name"));
// continue chain ...
}
}Changing the OnMatch value to NEUTRAL lets the log event continue to the normal root level check, demonstrating how filter decisions interact with standard log levels.
2.3 Custom TurboFilter
For finer‑grained control, a custom TurboFilter can be implemented. The example below filters based on log level, logger name, and MDC values, while allowing an admin user to bypass restrictions.
public class PackTurboFilter extends TurboFilter {
private static final String SENSITIVE_LOG = "com.pack.sensitive";
@Override
public FilterReply decide(Marker marker, ch.qos.logback.classic.Logger logger,
Level level, String format, Object[] params, Throwable t) {
if (level.isGreaterOrEqual(Level.WARN)) {
return FilterReply.ACCEPT;
}
if (SENSITIVE_LOG.equals(logger.getName())) {
return FilterReply.DENY;
}
String userId = getContext().getCopyOfPropertyMap().get("userId");
if ("admin".equals(userId)) {
return FilterReply.ACCEPT;
}
return FilterReply.NEUTRAL;
}
}Register the custom filter in logback-spring.xml:
<configuration>
<turboFilter class="com.pack.mdc.filter.PackTurboFilter"/>
</configuration>Considerations
TurboFilter introduces minimal synchronization overhead but can increase memory usage if state is stored.
Improper rules may unintentionally discard important logs.
Always benchmark performance when adding early‑stage filters.
When used judiciously, MDC‑based request tracing and TurboFilter provide powerful, low‑overhead mechanisms for making log data more observable and manageable in Spring Boot applications.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
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.
