MDC介绍 -- 一种多线程下日志管理实践方式

一:MDC介绍

  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类的内部类

    }
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值