当前位置: 首页 > 知识库问答 >
问题:

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

钱德元
2023-03-14

如何保留在javax的doFilter()方法中添加的MDC属性。servlet。过滤器实现。。。

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() {
    }
}

事件的顺序是:

  1. 请求已收到
  2. 调用MydoFilter(),将随机令牌添加到MDC
  3. 通过调用chain来处理请求。doFilter()
  4. 无论发生什么情况(处理完成,出现错误),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块,从而清除MDC。

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

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

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


共有3个答案

东郭远航
2023-03-14

您可以使用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();
    }
}
苏宏逸
2023-03-14

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

第一种方法是定义一个过滤器,并像这样包装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的映射url

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

如果您可以更改DispatcherServlet的url映射,并且您应该有如下默认的异常处理程序定义,那么这种方式是适用的:

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

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

第二种方法是使用拦截器,配置如下:

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

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

通俊发
2023-03-14

它似乎工作得很好,清除MDC上的ServletRecestListener#请求销毁(),而不是过滤器

(以下是具体的例子。)

 类似资料:
  • 本文向大家介绍Springboot之自定义全局异常处理的实现,包括了Springboot之自定义全局异常处理的实现的使用技巧和注意事项,需要的朋友参考一下 前言: 在实际的应用开发中,很多时候往往因为一些不可控的因素导致程序出现一些错误,这个时候就要及时把异常信息反馈给客户端,便于客户端能够及时地进行处理,而针对代码导致的异常,我们一般有两种处理方式,一种是throws直接抛出,一种是使用try.

  • 问题内容: 我们正在使用Spring MVC + Spring Security + Hibernate创建一个RESTful API。该API可以生成JSON和HTML。做好弹簧安全性的错误处理让我头疼: 身份验证可以通过多种方式进行:BasicAuth,通过POST请求中的不同参数以及通过Web登录。对于每种身份验证机制,在Spring Security xml配置的名称空间元素中声明了一个过

  • 我来找你是因为我需要你的帮助。 在我的文件camel-context.xml中,我调用了一个存储过程来获取用户的信息。我需要解决的是: 如果存储过程不返回数据,则发送带有超文本传输协议代码404的UserNot的异常(我已经拥有该类)。 我一直在寻找如何做这件事,但找不到任何可以帮助我的东西。我刚刚开始使用这个框架,所以提前非常感谢您的帮助。 这里我的代码:

  • 我使用netbeans创建了这个异常类: 当我尝试编译时,问题变得更加突出,在setter方法中,我得到以下消息: 错误:未报告异常异常;必须捕获或声明被抛出抛出新车辆异常(2,matricula);C:\users\ivan\desktop\examen isidrer\m03-uf5\exmaenm03uf5\src\info\infomila\vehicle.java:55:错误:未报告的异

  • 我使用的是Nifi 0.4.1版本。我写自定义代码转换CSV到avro格式。我已经创建了类文件,并能够生成nar文件。将nar文件放置在lib目录中,并重新启动nifi服务器。 任何帮助都很感激.. 谢谢,

  • 我的要求是,如果post请求的JSON无效,我将需要发送400个HTTP响应代码,如果任何字段不可解析,返回状态代码将为422。例如,post请求可以是: Dto类提供如下:, 这是发出POST请求的控制器, 如果“金额”是,比如说,“sfdfd”,这不是大小数,我们应该提供422。但如果“金额”为“-12.3343”,则这是约束验证错误,但数据有效且可分析。所以我们不能拥有422。 这是我的异常