在Spring Boot中的异常处理期间保留自定义MDC属性 [英] Preserve custom MDC attributes during exception-handling in Spring Boot

查看:149
本文介绍了在Spring Boot中的异常处理期间保留自定义MDC属性的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

How to preserve the MDC attribute added in a doFilter() method of a javax.servlet.Filter implementation ...

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(...))中发生异常.

... during exception handling if an exception occurs in another filter or in the Controller (the call to chain.doFilter(...)).

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

Currently, if an exception occurs: the finally block would be executed, clearing the MDC, and then the exception would be thrown out of the filter. All the logs during the exception handling will not contain the MDC attribute.

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

I have a simple Filter implementation to intercept all requests. It only creates a random string of characters (a token) to be included in all logs related to processing the request.

@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. 调用我的 doFilter(),将随机令牌添加到MDC.
  3. 通过调用 chain.doFilter()处理请求.
  4. 无论发生什么情况(处理完成,发生错误),都会清除MDC中的 finally 块中的随机令牌.
  1. The request is received.
  2. My doFilter() is called, adding the random token to MDC.
  3. The request is processed by calling chain.doFilter().
  4. Whatever happens (processing finished, error occurred), the MDC is cleared of the random token in the finally block.

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

The problem is that if an error occurs and it is processed (e.g. by a custom ErrorController implementation), the related logs do not include the token:

[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

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

The finally block is executed when the exception is thrown from the Controller which handles it, resulting in clearing the MDC.

此后将执行错误处理(包括自定义的 ErrorController ),这意味着相关日志中不再有令牌.

Error handling (including the custom ErrorController) is executed after this, which means there is no more token in the related logs.

如何从接收请求直到发送响应(包括错误处理),向与请求的整个处理相关的日志 all 添加自定义标记?我希望在线程发送响应后清除MDC,作为最后一个动作.无论发生什么情况(成功的响应,在错误处理期间引发异常等),都应清除MDC.

How can I add a custom token to all the logs related to the entire processing of the request from receiving it until sending a response, including error handling? I want the MDC to be cleared after a response has been sent by the thread, as the last action. The MDC should be cleared regardless of what happens (successful response, exception thrown during error handling, etc).

当多个客户端同时使用Rest服务时,日志可能会变得很混乱.在特定请求的整个处理过程中,将唯一的令牌附加到所生成的每个日志中,将大大简化调试.

With multiple clients using the Rest service simultaneously, logs can get really messed up. Having a unique token attached to each log produced during the entire handling process of a certain request would greatly simplify debugging.

推荐答案

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

There are two ways to define mechanism for generating token for requests.

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

The first way is to define a filter and wrap the DispatcherServlet like this:

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

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

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

That way is applicable if you can change url mapping for DispatcherServlet, and you should have default exception handler definition like this:

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

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

otherwise can be logs without token in console. If the above conditions do not apply, use the second method.

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

The second way is to use the Interceptor, the configuration is as follows:

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.第二种方法涵盖了在日志消息中添加令牌的所有情况.

Above configuration has MDC.clear() in postHandle method, because after exception the afterCompletion method is executed immediately and will clear the MDC. The second method covers all cases of adding a token to log messages.

这篇关于在Spring Boot中的异常处理期间保留自定义MDC属性的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆