How can I make the MDC available during logging in a spring controller advice?
Asked Answered
B

1

13

I have a spring-boot web application which makes use of logback's MDC context to enrich the log with custom data. I have the following implementation in place which makes available some custom data associated with "customKey" and it properly gets logged after adding %X{customKey} to the logging pattern in the logback configuration:

public class MDCFilter extends OncePerRequestFilter implements Ordered {

@Override
protected void doFilterInternal(HttpServletRequest httpServletRequest,
        HttpServletResponse httpServletResponse,
        FilterChain filterChain) throws ServletException, IOException {
        try {

            MDC.put("customKey", "someValue");          

            filterChain.doFilter(httpServletRequest, httpServletResponse);
        } catch(Throwable t) {
            LOG.error("Uncaught exception occurred", t);
            throw t;
        } finally {
            MDC.remove("customKey");
        }
    }

    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE - 4;
    }
}

This works fine as long as no uncaught exceptions are being thrown. To handle these I have a controller advice in place. Sadly the MDC is not available during logging in the controller advice anymore since it has already been cleaned up. If I understand correctly spring determines the responsible ExceptionHandler by using the HandlerExceptionResolverComposite - implementation which registers itself with the lowest precedence - hence it comes last after the MDC has already been cleaned up.

My qurestion now is: How should I register my filter so that the MDC is still available during logging in the controller advice?

I think one option would be to remove the MDC.remove(...) call from the finally block of the filter and instead implement a ServletRequestListener which does the cleanup of the MDC in the requestDestroyed - method. But since the filter is used in multiple web modules I would need to make sure that the ServletRequestListener is also declared in every existing and prospective module along with the MDCFilter which seems kind of error-prone to me. Moreover I would prefer it if the filter responsible for adding data to the MDC also takes care of its removal.

Brachypterous answered 30/5, 2017 at 8:46 Comment(2)
I know this is an old question, but did you get any further insight on this? I'm hitting the same problem. Furthermore, the HttpServletResponse status is always 200 in this filter when an unchecked exception is thrown.Unseen
Just create the MDCFilter as a @Component or a @Bean and MDC would be available in the controller advice.Stereoisomer
M
2

I came across a similar situation where I couldn't get the MDC context value in my logging filter which I am set in my some random service class - and I need to pass this with my response headers. So I solved the issue using a controller advice.

Not sure whether this solves your problem - But I used controller advise for this scenario.

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

@ControllerAdvice
public class ResponseAdvice implements ResponseBodyAdvice<Object> {

    private static final Logger LOGGER = LoggerFactory.getLogger(ResponseAdvice.class);


    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        return true;
    }

    @Override
    public Object beforeBodyWrite(Object body, MethodParameter returnType,
                                  MediaType selectedContentType,
                                  Class<? extends HttpMessageConverter<?>> selectedConverterType,
                                  ServerHttpRequest request, ServerHttpResponse response) {

        response.getHeaders().add("ThirdParty-Request-Id", MDC.get("ThirdParty-Request-Id"));
        response.getHeaders().add("ThirdParty-Response-Date", MDC.get("ThirdParty-Date"));

        return body;
    }
}

My logging filter looks like below,

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import jakarta.servlet.Filter;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.util.UUID;

@Component
@Order(1)
public class LoggingFilter implements Filter {
    private static final Logger LOGGER = LoggerFactory.getLogger(LoggingFilter.class);

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest req = (HttpServletRequest) servletRequest;

MDC.put("key", value);
}
}
Mylo answered 19/2 at 11:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.