logback的MDC机制
1. MDC 介绍:
MDC(Mapped Diagnostic Context,映射调试上下文),即将一些运行时的上下文数据通过logback打印出来,是 一种方便在多线程条件下记录日志的功能。和SiftingAppender一起,可以实现根据运行时的上下文数据,将日志保存到不同的文件中。
MDC(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。某些应用程序采用多线程的方式来处理多个用户的请求。在一个用户的使用过程中,可能有多个不同的线程来进行处理。典型的例子是 Web 应用服务器。当用户访问某个页面时,应用服务器可能会创建一个新的线程来处理该请求,也可能从线程池中复用已有的线程。在一个用户的会话存续期间,可能有多个线程处理过该用户的请求。这使得比较难以区分不同用户所对应的日志。当需要追踪某个用户在系统中的相关日志记录时,就会变得很麻烦。
一种解决的办法是采用自定义的日志格式,把用户的信息采用某种方式编码在日志记录中。这种方式的问题在于要求在每个使用日志记录器的类中,都可以访问到用户相关的信息。这样才可能在记录日志时使用。这样的条件通常是比较难以满足的。MDC 的作用是解决这个问题。
MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。
为了验证MDC的正确性,我写了个简单的多线程程序,代码如下:
import org.apache.log4j.MDC;
public class ThreadTest extends Thread {
private int i ;
public ThreadTest(){
}
public ThreadTest(int i){
this.i = i;
}
public void run(){
System.out.println(++i);
MDC.put("username", i);
for (int j = 0; j < 100; j++) {
System.out.println("aaa" + i);
if(j==10){
try {
this.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("run: " + i + " " + MDC.get("username"));
}
public static void main(String args[]) throws InterruptedException{
ThreadTest t1 = new ThreadTest(1);
t1.start();
ThreadTest t2 = new ThreadTest(2);
t2.start();
}
}
输出结果是:
2
3
aaa3
aaa3
aaa2
aaa3
aaa2
aaa3
aaa2
aaa3
aaa2
aaa3
aaa2
aaa3
aaa2
aaa2
aaa2
aaa2
aaa2
run: 2 2
aaa3
aaa3
aaa3
run: 3 3
从结果中可以看出:进程t1与t2在MDC中的值是没有相互影响的,确保了多进程下进程之间在MDC存放的值是没有相互的影响的或者说是无关的(进程t1在MDC中的username的键值为2;进程t2在MDC中的username的键值为3)。
分析:
MDC类put方法:
public static void put(String key, Object o)
{
mdc.put0(key, o);
}
private void put0(String key, Object o)
{
if (this.java1) {
return;
}
Hashtable ht = (Hashtable)((ThreadLocalMap)this.tlm).get();
if (ht == null) {
ht = new Hashtable(7);
((ThreadLocalMap)this.tlm).set(ht);
}
ht.put(key, o);
}
结合类java.lang.ThreadLocal<T>及Thread类可以知道,MDC中的put方法其实就是讲键值对放入一个Hashtable对象中,然后赋值给当前线程的ThreadLocal.ThreadLocalMap对象,即threadLocals,这保证了各个线程的在MDC键值对的独立性。
下边为java.lang.ThreadLocal<T>的部分代码:
public class ThreadLocal<T> {
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
}
Thread类的部分代码:
public class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
......................
.........................
static class ThreadLocalMap { //ThreadLocalMap为Thread类的内部类
}
}