How to preserve the MDC attribute added in a doFilter()
method of a javax.servlet.Filter
implementation ...
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
MDC.put("token", MyToken.random()); // add the MDC attribute related to the current request processing
chain.doFilter(request, response); // send the request to other filters and the Controller
} finally {
MDC.clear(); // MDC attribute must be removed so future tasks executed on the same thread would not log invalid information
}
}
... during exception handling if an exception occurs in another filter or in the Controller (the call to chain.doFilter(...)
).
Currently, if an exception occurs: the finally block would be executed, clearing the MDC, and then the exception would be thrown out of the filter. All the logs during the exception handling will not contain the MDC attribute.
I have a simple Filter
implementation to intercept all requests. It only creates a random string of characters (a token) to be included in all logs related to processing the request.
@Component
@Order(Ordered.HIGHEST_PRECEDENCE)
public class RequestFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
MDC.put("token", MyToken.random());
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
@Override
public void destroy() {
}
}
The sequence of events would be:
doFilter()
is called, adding the random token to MDC.chain.doFilter()
.finally
block. The problem is that if an error occurs and it is processed (eg by a custom ErrorController
implementation), the related logs do not include the token:
[2019.03.13 15:00:14.535] token:308...8bf [DEBUG] 8124 [https-jsse-nio-8443-exec-7] o.s.w.s.DispatcherServlet : GET "/resource", parameters={}
[2019.03.13 15:00:14.551] token:308...8bf [DEBUG] 8124 [https-jsse-nio-8443-exec-7] o.s.w.s.DispatcherServlet : Completed 400 BAD_REQUEST
[2019.03.13 15:00:14.551] token: [DEBUG] 8124 [https-jsse-nio-8443-exec-7] o.s.w.s.DispatcherServlet : "ERROR" dispatch for GET "/error", parameters={}
[2019.03.13 15:00:14.551] token: [DEBUG] 8124 [https-jsse-nio-8443-exec-7] o.s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public org.springframework.http.ResponseEntity myproj.CustomErrorController.handleError(javax.servlet.http.HttpServletRequest)
[2019.03.13 15:00:14.551] token: [ERROR] 8124 [https-jsse-nio-8443-exec-7] m.CustomErrorController : HTTP Error: Bad Request (400)
[2019.03.13 15:00:14.551] token: [DEBUG] 8124 [https-jsse-nio-8443-exec-7] o.s.w.s.DispatcherServlet : Exiting from "ERROR" dispatch, status 400
The finally
block is executed when the exception is thrown from the Controller
which handles it, resulting in clearing the MDC.
Error handling (including the custom ErrorController
) is executed after this, which means there is no more token in the related logs.
How can I add a custom token to all the logs related to the entire processing of the request from receiving it until sending a response, including error handling? I want the MDC to be cleared after a response has been sent by the thread, as the last action. The MDC should be cleared regardless of what happens (successful response, exception thrown during error handling, etc).
With multiple clients using the Rest service simultaneously, logs can get really messed up. Having a unique token attached to each log produced during the entire handling process of a certain request would greatly simplify debugging.
It seems to work well that clearing MDC on ServletRequestListener#requestDestroyed()
, instead of on Filter
.
( Here is concrete example.)
There are two ways to define mechanism for generating token
for requests.
The first way is to define a filter and wrap the DispatcherServlet
like this:
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import java.io.IOException;
import java.util.UUID;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
@Component
@WebFilter
public class RequestFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
try {
MDC.put("token", UUID.randomUUID().toString());
chain.doFilter(request, response);
} finally {
MDC.clear();
}
}
}
and change mapping url for DispatcherServlet
in application.properties
server.servlet.context-path=/api
spring.mvc.servlet.path=/
That way is applicable if you can change url mapping for DispatcherServlet
, and you should have default exception handler definition like this:
@ExceptionHandler({ Exception.class })
public void handleException(Exception e) {
log.error("Error: ", e);
}
otherwise can be logs without token in console. If the above conditions do not apply, use the second method.
The second way is to use the Interceptor
, the configuration is as follows:
public class MDCInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
MDC.put("token", UUID.randomUUID().toString());
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
MDC.clear();
}
}
And add interceptor in configuration
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MDCInterceptor());
}
}
Above configuration has MDC.clear()
in postHandle
method, because after exception the afterCompletion
method is executed immediately and will clear the MDC. The second method covers all cases of adding a token to log messages.
you can use ServletRequestListener instead,
for example:
import org.slf4j.MDC;
import org.springframework.stereotype.Component;
import javax.servlet.ServletRequestEvent;
import javax.servlet.ServletRequestListener;
import java.util.UUID;
@Component
public class MyServletRequestListener implements ServletRequestListener {
@Override
public void requestInitialized(ServletRequestEvent requestEvent) {
MDC.put("token", UUID.randomUUID().toString());
}
@Override
public void requestDestroyed(ServletRequestEvent requestEvent) {
MDC.clear();
}
}
A standard servlet filter is executed around any servlet, including Spring's DispatcherServlet
(see, for example, here ) but your filter is a Spring component. Since it doesn't use any Spring bean you can easily convert it to a plain filter, that is a filter configured in the web.xml
as described in the page I linked.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.