I'm working on a Reactive Spring Boot application that uses Log4j2 as the logging framework. By default, my application logs at the INFO
level, and I set the logging level in the application.yml
I would like to be able to log at the DEBUG
level for only one specific request without changing the logging level globally or affecting other concurrent requests. For example, if I pass a custom header (e.g., X-Debug-Logging: true
) for a request, I want that request to log at the DEBUG
level, but the other parallel requests should continue logging at the default INFO
level.
What I've Tried: I have implemented a WebFilter to check for the X-Debug-Logging header and adjust the logging level, but I'm concerned that since Reactive Spring handles requests asynchronously, changing the log level could affect other requests that are processed concurrently.
Question:
How can I dynamically log at the DEBUG
level for a single request in a Reactive Spring application, using Log4j2, while ensuring that other concurrent requests continue logging at the default INFO
level?
Use cases:
X-Debug-Logging: true
, it should log at DEBUG
.INFO
(default).What you are looking for is the DynamicThresholdFilter
or ContextMapFilter
, which allows you to filter messages based on the current context data.
When filters are used as global filters (i.e. direct children of <Configuration
>):
ACCEPT
will accept the message unconditionally,NEUTRAL
will also check the configured logger level.(see Logger stage for details).
So all you need to do is:
sampled
) and propagate it to all the threads, were the request will be handled.ACCEPT
if sampled
is true and NEUTRAL
otherwise.As you noticed, propagating the value is probably the hardest task. For that I suggest you to read Dariusz blog series on context propagation. In this particular case you need to:
io.micrometer:context-propagation
to your application:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>context-propagation</artifactId>
</dependency>
main
method of your application:
Hooks.enableAutomaticContextPropagation();
ThreadLocalAccessor
for the Log4j ThreadContext
in your main
method. There is no direct accessor, but the SLF4J MDC
is bridged to ThreadContext
:
ContextRegistry.getInstance().registerThreadLocalAccessor(new Slf4jThreadLocalAccessor());
WebFilter
that sets and removes the sampled
key from the thread context map:
@Component
public class TracingFilter implements WebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
boolean sampled =
Boolean.parseBoolean(exchange.getRequest().getHeaders().getFirst("X-Debug-Logging"));
return chain.filter(exchange).contextWrite(c -> {
// Get a copy of the MDC map from the Reactor context
Map<String, Object> mdc =
new HashMap<>(c.getOrDefault(Slf4jThreadLocalAccessor.KEY, Collections.emptyMap()));
// Add or remove an entry
mdc.put("sampled", sampled ? "true" : null);
// Return a modified context
return c.put(Slf4jThreadLocalAccessor.KEY, Collections.unmodifiableMap(mdc));
});
}
}
You can use DynamicThresholdFilter
, which:
DEBUG
if sampled
is true
,defaultThreshold
if sampled
has any other non-null
value,onMismatch
otherwise.<Configuration xmlns="https://logging.apache.org/xml/ns"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
https://logging.apache.org/xml/ns
https://logging.apache.org/xml/ns/log4j-config-2.xsd">
<DynamicThresholdFilter key="sampled"
onMatch="ACCEPT"
onMismatch="NEUTRAL">
<KeyValuePair key="true" value="DEBUG"/>
</DynamicThresholdFilter>
<!-- Your `Appenders` and `Loggers` go here: -->
</Configuration>