登录Spring Controller建议期间如何使MDC可用?

jim*_*arn 6 logback mdc spring-boot

我有一个spring-boot Web应用程序,该应用程序利用logback的MDC上下文来用自定义数据丰富日志。我具有以下实现方式,该实现方式可以使用一些与“ customKey”关联的自定义数据,并且在将%X {customKey}添加到logback配置中的日志记录模式后,它会被正确记录:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

只要没有抛出未捕获的异常,此方法就可以正常工作。为了解决这些问题,我准备了控制器建议。遗憾的是,MDC在登录控制器建议期间不再可用,因为它已被清理。如果我正确理解,spring将通过使用HandlerExceptionResolverComposite来确定负责的ExceptionHandler,该实现以最低的优先级进行注册,因此,它是在MDC已清理之后的最后一个。

我现在的困惑是:我应该如何注册我的过滤器,以便在登录控制器建议期间仍可以使用MDC?

我认为一个选择是从过滤器的finally块中删除MDC.remove(...)调用,而是实现一个ServletRequestListener,它在requestDestroyed-方法中清除MDC。但是由于该过滤器用于多个Web模块,所以我需要确保在每个现有的预期模块以及MDCFilter中也声明了ServletRequestListener,这对我来说似乎很容易出错。此外,如果负责将数据添加到MDC的过滤器还负责删除它,我希望使用它。

小智 1

我遇到了类似的情况,我无法在我的日志过滤器中获取 MDC 上下文值,该值是在我的一些随机服务类中设置的 - 我需要将其与我的响应标头一起传递。所以我使用控制器建议解决了这个问题。

不确定这是否能解决您的问题 - 但我在这种情况下使用了控制器建议。

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

我的日志过滤器如下所示,

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);
}
}
Run Code Online (Sandbox Code Playgroud)