使用 Logback MDC 进行 Spring Boot ErrorController 日志记录

2024-02-18

(更新:我的问题似乎与this one https://stackoverflow.com/q/55146885/4506703,但没有有效的答案。)

我正在尝试登录 Spring BootErrorController,但它的日志没有 MDC 值。

@Controller
@RequestMapping("/error")
@RequiredArgsConstructor
@Slf4j
public class MyErrorController implements ErrorController {

    private final ErrorAttributes errorAttributes;

    @Override
    public String getErrorPath() {
        return null;
    }

    @RequestMapping
    @ResponseBody
    public Map<String, String> error(final HttpServletRequest request) {
        final ServletWebRequest webRequest = new ServletWebRequest(request);
        final Throwable th = errorAttributes.getError(webRequest);

        if (th != null) {
            // **Logged without "requestId" value**
            log.error("MyErrorController", th);
        }

        return Map.of("result", "error");
    }
}
// http://logback.qos.ch/manual/mdc.html#autoMDC
public class MDCFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
        final FilterChain filterChain)
        throws ServletException, IOException {

        final String requestId = UUID.randomUUID().toString();

        MDC.put("requestId", requestId);

        try {
            filterChain.doFilter(request, response);
        } finally {
            MDC.remove("requestId");
        }
    }

}
@Configuration
public class MyConfig {

    @Bean
    public FilterRegistrationBean<MDCFilter> mdcFilter() {
        final FilterRegistrationBean<MDCFilter> bean = new FilterRegistrationBean<>();
        bean.setFilter(new MDCFilter());
        bean.addUrlPatterns("/*");
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
    }
}

logback-spring.xml:

<configuration>
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <!-- encoders are assigned the type
         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
        <encoder>
            <pattern>%d{HH:mm:ss.SSS} [%thread] requestId:%X{requestId} %-5level %logger{36} - %msg%n</pattern>
        </encoder>
    </appender>

    <root level="debug">
        <appender-ref ref="STDOUT" />
    </root>
</configuration>

result(requestId值不出现):

18:15:13.705 [http-nio-8080-exec-1] requestId: ERROR c.e.l.MyErrorController - MyErrorController
java.lang.RuntimeException: Error occured.
...

这是完整的代码 https://github.com/yukihane/logging-with-mdc-demo.

我认为我需要适应MDCFilter before DispatcherServlet,但我不知道该怎么做。


删除 MDC 数据ServletRequestListener#requestDestroyed() https://jakarta.ee/specifications/platform/8/apidocs/javax/servlet/servletrequestlistener#requestDestroyed-javax.servlet.ServletRequestEvent-而不是在Filter.

在雄猫上标准主阀 https://github.com/apache/tomcat/blob/9.0.44/java/org/apache/catalina/core/StandardHostValve.java#L169-L186 fires RequestDestroyEvent after ErrorController执行。

            // Look for (and render if found) an application level error page
            if (response.isErrorReportRequired()) {
                // If an error has occurred that prevents further I/O, don't waste time
                // producing an error report that will never be read
                AtomicBoolean result = new AtomicBoolean(false);
                response.getCoyoteResponse().action(ActionCode.IS_IO_ALLOWED, result);
                if (result.get()) {
                    if (t != null) {
                        throwable(request, response, t); // *ErrorController is executed*
                    } else {
                        status(request, response);
                    }
                }
            }

            if (!request.isAsync() && !asyncAtStart) {
                context.fireRequestDestroyEvent(request.getRequest()); // *RequestDestroyEvent is fired*
            }

因此,执行以下操作:

public class MDCClearListener implements ServletRequestListener {

    @Override
    public void requestDestroyed(final ServletRequestEvent sre) {
        MDC.remove("requestId");
    }
}
    @Bean
    public ServletListenerRegistrationBean<MDCClearListener> mdcClearListener() {
        final ServletListenerRegistrationBean<MDCClearListener> bean = new ServletListenerRegistrationBean<>();
        bean.setListener(new MDCClearListener());
        bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
        return bean;
    }

(具体代码存在于solution branch https://github.com/yukihane/logging-with-mdc-demo/tree/solution.)


关于相关问题解答

这个答案 https://stackoverflow.com/a/66913638/4506703不适合我。因为:

第一种方式不使用ErrorController but @ExceptionHandler,所以无法捕获Spring Security抛出的异常Filter. (Try answer/exceptionhandler-with-springsecurity branch https://github.com/yukihane/logging-with-mdc-demo/tree/answer/exceptionhandler-with-springsecurity code.)

第二种方式将 UUID 放在拦截器上,因此记录了不同的 requestIdMyController and MyErrorController。这不是“请求”ID。 (尝试answer/interceptor branch https://github.com/yukihane/logging-with-mdc-demo/tree/answer/interceptor code.)

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

使用 Logback MDC 进行 Spring Boot ErrorController 日志记录 的相关文章

随机推荐