在 Spring Boot 中的异常处理期间保留自定义 MDC 属性

     2023-02-24     264

关键词:

【中文标题】在 Spring Boot 中的异常处理期间保留自定义 MDC 属性【英文标题】:Preserve custom MDC attributes during exception-handling in Spring Boot 【发布时间】:2019-08-04 10:12:01 【问题描述】:

短版(有足够的细节)

如何保留在javax.servlet.Filter 实现的doFilter() 方法中添加的MDC 属性...

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
    

...如果在另一个过滤器或控制器中发生异常(调用chain.doFilter(...)),则在异常处理期间。

目前,如果发生异常:将执行 finally 块,清除 MDC,然后将异常从过滤器中抛出。异常处理期间的所有日志都不会包含MDC属性。

加长版(过于详细)

我有一个简单的Filter 实现来拦截所有请求。它只创建一个随机字符串(一个令牌)以包含在与处理请求相关的所有日志中。

@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() 
    

事件的顺序是:

    请求已收到。 我的 doFilter() 被调用,将随机令牌添加到 MDC。 通过调用chain.doFilter()处理请求。 无论发生什么(处理完成,发生错误),MDC 都会清除 finally 块中的随机令牌。

问题是如果发生错误并被处理(例如通过自定义ErrorController 实现),相关日志不包含令牌:

[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

finally 块在处理它的Controller 抛出异常时执行,从而清除 MDC。

在此之后执行错误处理(包括自定义ErrorController),这意味着相关日志中没有更多的令牌。

如何将自定义令牌添加到所有与从接收请求到发送响应的整个处理请求相关的日志,包括错误处理? 我希望在线程发送响应后清除 MDC,作为最后一个操作。无论发生什么(成功响应、错误处理期间引发的异常等),都应清除 MDC。

如果多个客户端同时使用 Rest 服务,日志可能会变得非常混乱。在某个请求的整个处理过程中产生的每个日志都附加一个唯一的令牌将大大简化调试。

【问题讨论】:

您的代码对我来说似乎没问题。根据您的过滤器配置,您的 MDC 会在一切完成后被清除。所有调度程序 servlet 处理都发生在您的“finally”块执行之前。因此,从逻辑上讲,您的 trace-id 应该打印在所有日志中。您能否在“finally”块中添加一个日志行以了解 MDC 被清除的顺序?如果我是正确的,您的日志行将在最后打印,表明您的代码中的其他位置,MDC 也被清除。 @MukulBansal 我将在今天晚些时候对此进行调查并更新问题。我现在相信,通过向 Spring Security 添加自定义过滤器,在配置中指定显式顺序,可以实现上述目标。然后 MDC 被一个更接近执行链开始的过滤器设置和清除,可能还包装了异常处理。但我不确定,也许效果完全一样,异常处理总是在其他过滤器退出后进行。 查看FilterChainProxy 的来源,您的过滤器可能已添加到安全过滤器链(虚拟链)中。这可以解释日志输出。 【参考方案1】:

ServletRequestListener#requestDestroyed()而不是Filter上清除MDC似乎效果很好。

(Here是具体例子。)

【讨论】:

【参考方案2】:

有两种方法可以定义为请求生成token 的机制。

第一种方法是定义一个过滤器并像这样包装DispatcherServlet

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();
        
    


并在application.properties 中更改DispatcherServlet 的映射网址

server.servlet.context-path=/api
spring.mvc.servlet.path=/

如果您可以更改 DispatcherServlet 的 url 映射,则该方法适用,并且您应该具有如下默认异常处理程序定义:

@ExceptionHandler( Exception.class )
public void handleException(Exception e) 
   log.error("Error: ", e);

否则可以是控制台中没有令牌的日志。 如果上述条件不适用,请使用第二种方法。

第二种方式是使用Interceptor,配置如下:

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();
    

并在配置中添加拦截器

@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer 

    @Override
    public void addInterceptors(InterceptorRegistry registry) 
        registry.addInterceptor(new MDCInterceptor());
    

上面的配置在postHandle方法中有MDC.clear(),因为异常后afterCompletion方法会立即执行,并会清除MDC。第二种方法涵盖了将令牌添加到日志消息的所有情况。

【讨论】:

【参考方案3】:

您可以改用 ServletRequestListener,

例如:

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();
    

【讨论】:

【参考方案4】:

标准 servlet 过滤器围绕 任何 servlet 执行,包括 Spring 的 DispatcherServlet(例如,参见 here),但您的过滤器是 Spring 组件。由于它不使用任何 Spring bean,因此您可以轻松地将其转换为普通过滤器,即在 web.xml 中配置的过滤器,如我链接的页面中所述。

【讨论】:

HandlerInterceptor 会更简单甚至可能吗?在preHandle() 中添加MDC 属性并在afterCompletion() 中删除它? 恐怕任何 Spring MVC 组件都由DispatcherServlet 管理(即包装),但您的用例需要包装 servlet 的东西,即标准过滤器。 @anddero:有什么反馈吗?您是否尝试过标准过滤器解决方案? 还没有,我会尽快回复,谢谢! @DaveC 正如我已经说过的:“恐怕任何 Spring MVC 组件都由 DispatcherServlet 管理(即包装),但您的用例需要包装 servlet 的东西,即标准过滤器”。 OP 已不再对此建议提供反馈,因此您可以成为第一个尝试的人。

在身份验证 Spring Security + WebFlux 期间抛出和处理自定义异常

】在身份验证SpringSecurity+WebFlux期间抛出和处理自定义异常【英文标题】:ThrowandhandlecustomexceptionduringauthenticationSpringSecurity+WebFlux【发布时间】:2019-06-0503:57:03【问题描述】:我试图在身份验证期间在WebFlux中引发自定义异常,并... 查看详情

在 Spring 中的 @Transactional 方法期间处理异常

】在Spring中的@Transactional方法期间处理异常【英文标题】:Handlingexceptionsduringa@TransactionalmethodinSpring【发布时间】:2018-10-1312:11:48【问题描述】:我试图找出如何结合Spring的@Transactional最好地处理持久性(以及可能的其他)异常。... 查看详情

Spring Boot 中的自定义异常

】SpringBoot中的自定义异常【英文标题】:CustomExceptioninSprinBoot【发布时间】:2019-12-1209:54:06【问题描述】:我在SPRINGBOOT中编写了以下自定义错误处理程序@RestControllerAdvicepublicclassCustomGlobalExceptionHandlerextendsResponseEntityExceptionHandler@... 查看详情

Spring Boot 中的生产级异常处理

】SpringBoot中的生产级异常处理【英文标题】:ProductionlevelExceptionhandlinginspringboot【发布时间】:2018-08-1701:01:14【问题描述】:我有一个场景:UI<--->Springbootmicro-serviceRESTAPI<--->server现在,有一种情况我想处理自定义异常(... 查看详情

Spring Boot 异常自定义处理 - 意外的 HTTP 状态

】SpringBoot异常自定义处理-意外的HTTP状态【英文标题】:SpringbootExceptioncustomhandling-UnexpectedHTTPstatus【发布时间】:2019-02-2502:14:41【问题描述】:我正在尝试在我的SpringBoot应用程序中实现一些自定义异常处理程序,这些处理程序... 查看详情

处理 Spring Boot 资源服务器中的安全异常

】处理SpringBoot资源服务器中的安全异常【英文标题】:HandleSecurityexceptionsinSpringBootResourceServer【发布时间】:2017-04-2916:03:40【问题描述】:如何让我的自定义ResponseEntityExceptionHandler或OAuth2ExceptionRenderer在纯资源服务器上处理Spring... 查看详情

Spring Boot Endpoints 中的异常处理

】SpringBootEndpoints中的异常处理【英文标题】:ExceptionhandlinginSpringBootEndpoints【发布时间】:2017-09-1421:47:22【问题描述】:我正在尝试处理具有SOAP端点和Rest控制器的SpringBoot应用程序中的异常。捕获发生在REST控制器中的异常非常... 查看详情

处理自定义转换器抛出的 Spring Boot REST 异常

】处理自定义转换器抛出的SpringBootREST异常【英文标题】:HandlingexceptioninSpringBootRESTthrownfromcustomconverter【发布时间】:2016-07-1110:57:42【问题描述】:我是SpringBoot的新手,但是在阅读了几个小时关于SpringBootREST中异常处理的帖子和... 查看详情

Spring Boot 异常处理数据库错误的自定义异常?

】SpringBoot异常处理数据库错误的自定义异常?【英文标题】:SpringBootExceptionHandlingcustomExceptionsforDatabaseErrors?【发布时间】:2022-01-1218:02:12【问题描述】:我为运行时可能发生的各种错误创建了多个自定义异常。为此,我使用了@... 查看详情

Spring Boot - 使用 RestControllerAdvice 的全局自定义异常处理机制

】SpringBoot-使用RestControllerAdvice的全局自定义异常处理机制【英文标题】:SpringBoot-GlobalCustomExceptionHandlingMechanismusingRestControllerAdvice【发布时间】:2018-01-0409:38:16【问题描述】:我正在为RestfulWeb服务使用SpringBoot。尝试设置一个全... 查看详情

测试期间spring webflux中的全局异常处理

】测试期间springwebflux中的全局异常处理【英文标题】:Globalexceptionhandlinginspringwebfluxduringtests【发布时间】:2020-03-2321:50:39【问题描述】:我正在使用springwebflux开发服务。我使用@ControllerAdvice实现了异常处理。它工作得很好,但... 查看详情

Spring Boot 中的文件上传:上传、验证和异常处理

】SpringBoot中的文件上传:上传、验证和异常处理【英文标题】:FileuploadinSpringBoot:Uploading,validation,andexceptionhandling【发布时间】:2017-03-1408:04:57【问题描述】:我希望能够将图像上传到服务器,优雅地处理错误和异常,并在表单... 查看详情

Spring Boot 从异常处理程序返回 401 状态自定义对象

】SpringBoot从异常处理程序返回401状态自定义对象【英文标题】:SpringBootReturning401StatusedCustomObjectFromExceptionHandler【发布时间】:2018-12-0714:25:26【问题描述】:我有一个SpringBootRESTAPI,它有一个LoginController用于执行简单的身份验证... 查看详情

缺少资源 bean:Spring Boot webflux 自定义全局异常处理程序

】缺少资源bean:SpringBootwebflux自定义全局异常处理程序【英文标题】:MissingResourcesbean:Springbootweb-fluxCustomGlobalExceptionhandler【发布时间】:2022-01-1503:47:19【问题描述】:我正在尝试通过扩展AbstractErrorWebExceptionHandler来实现我的自定... 查看详情

Spring Boot + 调度程序 + Spring Data JPA + Oracle 中的异常处理

】SpringBoot+调度程序+SpringDataJPA+Oracle中的异常处理【英文标题】:ExceptionHandlinginSpringBoot+Schedulers+SpringDataJPA+Oracle【发布时间】:2022-01-1212:58:10【问题描述】:我正在尝试找出所有异常可以被处理/捕获和处理的其他方式,例如将... 查看详情

Spring Boot 和 FlywayTest 导致 JPA Camel 路由在数据库重置期间抛出异常

】SpringBoot和FlywayTest导致JPACamel路由在数据库重置期间抛出异常【英文标题】:SpringBootandFlywayTestcauseJPACamelroutestothrowexceptionsduringdatabasereset【发布时间】:2015-04-0423:11:45【问题描述】:我有一个SpringBoot应用程序,其中包含一个带... 查看详情

如何使用 Spring Boot 对 WebFlux 进行异常处理?

】如何使用SpringBoot对WebFlux进行异常处理?【英文标题】:HowtodoExceptionHandlingforWebFluxusingSpringboot?【发布时间】:2020-01-2011:50:23【问题描述】:我有3个微服务应用程序。我正在尝试使用响应包中的webclient进行2获取异步调用,然后... 查看详情

在 Spring Boot 中自定义异常返回空消息 [重复]

】在SpringBoot中自定义异常返回空消息[重复]【英文标题】:CustomizedExceptionreturningemptymessageinSpringBoot[duplicate]【发布时间】:2020-11-2521:40:39【问题描述】:我创建了自定义异常ProduitIntrouvableException,它扩展了RuntimeException@ResponseStatu... 查看详情