Logback MDC——日志记录的多线程支持

MDC,映射调试上下文,跟着线程走的日志信息容器。

题图:from Google

引子

MDC(Mapped Diagnostic Context,映射调试上下文)是Logback提供的一种方便在多线程条件下记录日志的功能。使用MDC可以很方便的针对不同线程记录对应的日志信息。

一、MDC简介

Logback的设计目标之一就是为了帮助人们对复杂系统进行审计和调试,而实际运行的系统很多都会处理多线程。以web系统为例,每个请求都是一个单独的线程,如果需要针对不同线程记录对应的日志信息,应该怎么解决?

或许,我们可以为每一个线程配置不同的logger。但是这将导致logger数量大大增加,并且最终无法控制。

那么,有没有一种轻量级的、简单方便的解决方案呢?

有!那就是MDC。

如果把每个线程比作路上的行人,那么MDC就是每个人身上的背包,用来保存其专属的信息。

二、MDC的使用

MDC类只有静态方法,开发者可以把信息放进一个MDC,之后用其他logback组件获取这些信息。MDC是基于每个线程进行管理的。子线程自动继承其父的MDC的一个副本。换言之,MDC是线程安全的。比如,当一个web应用接收到一个请求时,开发者可以向MDC里插入恰当的信息,比如用户ip、请求参数等等。Logback组件会自动在每个记录条目里包含这些信息。

对于Servlet实现的web应用而言,针对每个请求进行特殊处理,采用filter的方式无疑最简单,以下就以一个简单filter演示MDC的使用。进行以下配置后,这次请求之内的日志输出都会带有MDC中存储的请求参数。

filter代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class LogFilter implements Filter {
@Override
public void init(FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
MDC.clear(); // 避免线程池线程重用导致的数据混乱
// 将请求参数保存在MDC中
MDC.put("utm_content", request.getParameter("utm_content"));
MDC.put("utm_term", request.getParameter("utm_term"));
MDC.put("utm_campaign", request.getParameter("utm_campaign"));
MDC.put("utm_media", request.getParameter("utm_media"));
MDC.put("utm_source", request.getParameter("utm_source"));
chain.doFilter(request, response);
}
@Override
public void destroy() {
}
}

logback配置(%mdc是将MDC中所有信息输出,也可以使用%mdc{utm_media,utm_source}方式指定输出内容)

1
2
3
4
5
6
7
8
9
10
11
<configuration>
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<layout class="ch.qos.logback.classic.PatternLayout">
<Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level - %mdc%n</Pattern>
</layout>
</appender>
<root value="debug">
<appender-ref ref="CONSOLE" />
</root>
</configuration>

三、MDC原理

在享受MDC带来的便利时,有没有想过它的实现原理是什么?

其实只要熟悉多线程使用,大家很容易想到一个词,ThreadLocal!没错,其实MDC就是一个ThreadLocal的HashMap。

具体实现并不复杂,大家可以跟踪一下MDC的源码即可,它的本体其实就是LogbackMDCAdapter这个类。