1.关于MDC
为什么要用log的MDC,它是用来解决什么问题?
多线程,高并发下,多个线程打印,无法形成连贯的思路。
MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。
MDC中的put方法其实就是讲键值对放入一个Hashtable对象中,然后赋值给当前线程的ThreadLocal.ThreadLocalMap对象,即threadLocals,这保证了各个线程的在MDC键值对的独立性。
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);
}
在 Web 应用中增加用户跟踪功能
参考资料:https://www.ibm.com/developerworks/cn/web/wa-lo-usertrack/index.html#fig1
1.2 子线程打印
我们知道MDC的本质实现是通过ThreadLocal实现的,那么如何实现子线程继承 父线程的信息。
基本原理:InheritableThreadLocal
一、基础知识:InheritableThreadLocal<T> extends ThreadLocal<T>
重写了父类的几个方法,使得创建子线程时,会继承父线程的ContextMap中的内容
测试案例
@Test
public void TestInheritableThreacLocal() {
final InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<String>();
inheritableThreadLocal.set("fatherName");
new Thread(new Runnable() {
public void run() {
String childThreadName = inheritableThreadLocal.get();
log.info("childThreadName is:{}", childThreadName);
} }).start();
try {
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
结果为
2019-02-25 10:43:44,300 [SpringClientMainTest] [Thread-1] INFO com.magpie.quickstarts.v2.SpringClientMainTest - childThreadName is:fatherName
可以继承父线程内容
MDC默认实现为ThreadLocal,但可以指定为InheritableThreadLocal
指定方式,官方有3重,1、JVM参数 2、环境变量 3、JAVA 系统变量,以下测试为java系统参数设置
XML配置文件格式
<Property name="pattern">%d{yyyy-MM-dd HH:mm:ss,SSS} %X{traceId} [%c{1}] [%t] %-5level %logger{36} - %msg%n</Property>
测试案例为:
static {
System.setProperty("isThreadContextMapInheritable", "true");
}
private static Logger log = LoggerFactory.getLogger(SpringClientMainTest.class);
@Test
public void testMDCInheritable() {
MDC.put("traceId", "123456");
log.info("Father thread traceId");
Thread childThread = new Thread(new Runnable() {
public void run() {
String childTraceId=MDC.get("traceId");
MDC.put("traceId",childTraceId);
while (true) {
log.info("Child thread traceId");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
childThread.start();
try {
log.info("Father thread traceId");
Thread.sleep(100000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
日志结果为:
2019-02-25 12:30:20,023 123456 [SpringClientMainTest] [main] INFO com.magpie.quickstarts.v2.SpringClientMainTest - Father thread traceId
2019-02-25 12:30:20,216 123456 [SpringClientMainTest] [main] INFO com.magpie.quickstarts.v2.SpringClientMainTest - Father thread traceId
2019-02-25 12:30:20,244 123456 [SpringClientMainTest] [Thread-1] INFO com.magpie.quickstarts.v2.SpringClientMainTest - Child thread traceId
2019-02-25 12:30:21,244 123456 [SpringClientMainTest] [Thread-1] INFO com.magpie.quickstarts.v2.SpringClientMainTest - Child thread traceId
TIP:不知道为什么,子线程中只能get一次,第二次就会为null,比较忙,等闲的时候排查一下代码
2.log4j2
log4j2 相比于1,配置等改动较大。生产比较实用的功能有:
- 异步打印,提升程序性能
- 动态配置,这一点对于某些debug日志平时,关闭,关键时刻打开非常方便
实际中,还有一个,我们一般将root设为一个级别,而在排查问题等时,将一些其他的可以针对性配置,如mybatis,你只需要
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
</Console>
</Appenders>
<Loggers>
<Logger name="xxx.xxx.db.mapper" level="debug" addivity="true">
</Logger>
<Root level="info">
<AppenderRef ref="Console"/>
</Root>
</Loggers>
</Configuration>
利用addictivity 多余性,可以直接在root的输出中添加进去,结合动态配置的功能,在生产排查问题时,就可以做到不停机的情况下,针对性排查了
2.1 异步打印深化
log4j2有多种异步打印方式,其中最推荐的一种需要依赖Disruptor框架。
Disruptor它是一个开源的并发框架,并获得2011 Duke’s 程序框架创新奖,能够在无锁的情况下实现网络的Queue并发操作
网友评论