题图:from Google
MDC(Mapped Diagnostic Context,映射调试上下文)是Logback提供的一种方便在多线程条件下记录日志的功能。使用MDC可以很方便的针对不同线程记录对应的日志信息。
Logback的设计目标之一就是为了帮助人们对复杂系统进行审计和调试,而实际运行的系统很多都会处理多线程。以web系统为例,每个请求都是一个单独的线程,如果需要针对不同线程记录对应的日志信息,应该怎么解决?
或许,我们可以为每一个线程配置不同的logger。但是这将导致logger数量大大增加,并且最终无法控制。
那么,有没有一种轻量级的、简单方便的解决方案呢?
有!那就是MDC。
如果把每个线程比作路上的行人,那么MDC就是每个人身上的背包,用来保存其专属的信息。
MDC类只有静态方法,开发者可以把信息放进一个MDC,之后用其他logback组件获取这些信息。MDC是基于每个线程进行管理的。子线程自动继承其父的MDC的一个副本。换言之, MDC是线程安全的 。比如,当一个web应用接收到一个请求时,开发者可以向MDC里插入恰当的信息,比如用户ip、请求参数等等。Logback组件会自动在每个记录条目里包含这些信息。
对于Servlet实现的web应用而言,针对每个请求进行特殊处理,采用filter的方式无疑最简单,以下就以一个简单filter演示MDC的使用。进行以下配置后,这次请求之内的日志输出都会带有MDC中存储的请求参数。
filter代码
1234567891011121314151617181920212223242526
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}方式指定输出内容)
1234567891011
<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带来的便利时,有没有想过它的实现原理是什么?
其实只要熟悉多线程使用,大家很容易想到一个词,ThreadLocal!没错,其实MDC就是一个ThreadLocal的HashMap。
具体实现并不复杂,大家可以跟踪一下MDC的源码即可,它的本体其实就是LogbackMDCAdapter这个类。