使用映射的诊断上下文MDC(Mapped Diagnostic Context)的Java日志记录

·  阅读 383
使用映射的诊断上下文MDC(Mapped Diagnostic Context)的Java日志记录

映射诊断上下文(Mapped Diagnostic Context),简称MDC,是一种用于区分来自不同来源的交错日志输出的工具。当一个服务器几乎同时处理多个客户时, 日志输出通常是交错的。

1.用MDC标记请求

MDC 被用来标记每个请求。它是通过将关于请求的上下文信息放入 MDC 来完成的。

MDC类包含以下静态方法

  • void put(String key, String val) :将一个由key标识的上下文值放入当前线程的上下文映射中。我们可以根据需要在MDC中放置尽可能多的值/键关联。
  • String get(String key) :获取由key参数确定的上下文值。
  • void remove(String key): 删除key参数所标识的上下文值。
  • void clear() :清除 MDC 中的所有条目。

使用 MDC API 对请求进行标记的示例是。

MDC.put("sessionId", "abcd");
MDC.put("userId", "1234");

2.在日志中打印 MDC 值

为了在日志中打印上下文信息,我们可以在日志模式字符串中使用 MDC 键

为了引用MDC上下文键,我们使用%X指定符,用于打印当前线程的嵌套诊断上下文(NDC)和/或映射诊断上下文(MDC)。

  • 使用%X ,包括地图的全部内容。
  • 使用%X{key} ,包括指定的键。
  • 使用%x ,以包括堆栈的全部内容。

例如,我们可以引用第一节中创建的userId和sessionId键,如下所示。在应用程序运行期间,MDC信息将使用给定的appended附加到每个日志消息中。

<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> 
  <layout>
    <Pattern>%d{DATE} %p %X{sessionId} %X{userId} %c - %m%n</Pattern>
  </layout> 
</appender>

3.使用Servet过滤器添加MDC

MDC 上下文信息任意地放在应用程序的几个地方并不是一个好主意。维护这样的代码会很困难。

由于MDC在本质上是线程本地的,我们可以使用Servlet过滤器作为添加MDC日志的好地方,因为Servlet使用单个线程来处理整个请求。通过这种方式,我们可以确保MDC信息不会与同一处理程序/控制器所处理的其他请求混在一起。

使用框架提供的Servlet过滤器来处理它,也使我们摆脱了线程安全或同步问题,因为框架会安全透明地处理这些问题。

Servlet容器是每个请求的线程。当一个HTTP请求被提出时,就会有一个线程被创建或从一个池子中检索出来来服务它。

因此,不要忘记在请求处理完成后清除MDC上下文,否则当同一个线程在下一次提供另一个请求时,它们的MDC信息将是陈旧的,并可能创建错误的日志条目。

import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import org.slf4j.MDC;
 
//To convert to a Spring Boot Filter 
//Uncomment @Component and Comment our @WebFilter annotation
//@Component 
@WebFilter( filterName = "mdcFilter", urlPatterns = { "/*" } )
public class MDCFilter implements Filter {
 
    @Override
    public void destroy() {
    }
 
    @Override
    public void doFilter( final ServletRequest request, 
    			final ServletResponse response, final FilterChain chain )
        throws IOException, ServletException {
 
        MDC.put( "sessionId", request.getParameter("traceId") );
 
        try {
            chain.doFilter( request, response );
        } finally {
            MDC.clear();
        }
    }
 
    @Override
    public void init( final FilterConfig filterConfig ) 
    	throws ServletException {
    }
}

注意,为了让Spring框架识别这个类是一个Web过滤器,我们需要用*@Component*注解将其定义为一个Bean。

4.带有日志框架的 MDC

4.1.使用 Log4J2 的 MDC

Log4j2同时支持MDC和NDC,但将它们合并为一个单一的ThreadContext类。Thread Context Map等同于MDC,Thread Context Stack等同于NDC。

要使MDC的副本自动继承到新创建的子线程,请启用 "isThreadContextMapInheritable" Log4j系统属性。

Log4j2 ThreadContext的一个例子。

import org.apache.logging.log4j.ThreadContext;

//Add information in context
ThreadContext.put("id", UUID.randomUUID().toString());
ThreadContext.put("ipAddress", request.getRemoteAddr());

//To clear context
ThreadContext.clear();

为了打印上下文的值,我们可以使用基于%X 的模式布局,正如在打印MDC值一节中讨论的那样。

我们可以在官方网站上阅读更多关于ThreadContextCloseableThreadContext的信息。

4.2.带有 SLF4J、Logback 和 Log4j 的 MDC

使用 SLF4J 的 MDC 取决于底层日志库对 MDC 的支持。如果底层库不支持 MDC,那么所有与 MDC 相关的语句都会被跳过,不会有任何副作用。

请注意,目前只有两个日志系统,即log4jlogback,提供MDC功能。对于不支持 MDC 的java.util.logging ,将使用BasicMDCAdapter。对于其他系统,将使用NOPMDCAdapter

上述框架在以下类中支持 MDC。

  • org.slf4j.MDC (SLF4J 和 Logback)
  • org.apache.log4j.MDC (Log4j)

一个SLF4J MDC的例子。

import org.slf4j.MDC;

//Add information in context
MDC.put("id", UUID.randomUUID().toString());
MDC.put("ipAddress", request.getRemoteAddr());

//To clear context
MDC.clear();

打印上下文值,我们需要使用前面讨论的基于%X 的模式布局。

我们可以在官方网站上阅读更多关于SLF4J和Logback中的MDC支持。

5.总结

映射诊断上下文(MDC)是一种很好的方式,可以在应用日志中添加更多的上下文信息,以达到改进请求跟踪的目的,特别是当应用是一个复杂的分布式应用时。

但是我们在并发环境中使用MDC时需要非常小心,因为线程是来自线程池的。在这种情况下,在处理完请求后,清除线程的上下文信息是非常重要的。

为了保持简单,建议使用基于SLF4J的MDC,因为它使用起来很简单,而且它使用底层日志框架对MDC日志的支持。

学习愉快!!

分类:
后端
标签:
分类:
后端
标签:
收藏成功!
已添加到「」, 点击更改