MDC是SLF4J中的一个类,通过MDC我们可以很方便的实现同一个线程内(包括父线程和子线程之间)的日志的追踪。对于Web项目来讲,在MDC的帮助下,很方便的帮我们找出一次请求中所有的日志。下面我们先来看一下一个简单的demo。
mdc-demo
1、首先我们需要一个Java的Maven项目这里我们取名为mdc-demo。
2、引入我们需要的依赖包slf4j和logback
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
3、新建logback.xml文件,配置日志打印格式,如果想打印MDC中的内容,需要在appender的pattern中配置。
<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{yyyy-MM-dd hh:mm:ss} [%thread] [traceId = %X{traceId}] [%logger{32}] - %msg%n</pattern>
</encoder>
</appender>
<root level="info">
<appender-ref ref="console"/>
</root>
</configuration>
补充以下,为什么配置文件要取名为logback.xml,因为logback在读取配置文件时会默认读取resources目录下的logback命名的xml文件。源码如下,在ContextInitializer的类中。
final public static String GROOVY_AUTOCONFIG_FILE = "logback.groovy";
final public static String AUTOCONFIG_FILE = "logback.xml";
final public static String TEST_AUTOCONFIG_FILE = "logback-test.xml";
final public static String CONFIG_FILE_PROPERTY = "logback.configurationFile";
public URL findURLOfDefaultConfigurationFile(boolean updateStatus) {
ClassLoader myClassLoader = Loader.getClassLoaderOfObject(this);
URL url = findConfigFileURLFromSystemProperties(myClassLoader, updateStatus);
if (url != null) {
return url;
}
url = getResource(TEST_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
url = getResource(GROOVY_AUTOCONFIG_FILE, myClassLoader, updateStatus);
if (url != null) {
return url;
}
return getResource(AUTOCONFIG_FILE, myClassLoader, updateStatus);
}
4、直接写Java代码就可以了,运行查看效果就可以了。
public class MdcTest {
private static Logger logger = LoggerFactory.getLogger(MdcTest.class);
public static void main(String[] args) {
MDC.put("traceId", UUID.randomUUID().toString());
logger.info("MdcTest");
MDC.clear();
logger.info("clear test");
MDC.remove("traceId");
}
}
输入结果如下,成功的打印出我们的traceId了。
2019-03-24 04:21:23 [main] [traceId = b48f3382-e71a-4fd1-a411-0e20661b2ccf] [com.tom.mdc.MdcTest] - MdcTest
2019-03-24 04:21:23 [main] [traceId = ] [com.tom.mdc.MdcTest] - clear test
这里我把MDC.put(key,value)中的key命名为了traceId,只要跟logback.xml中的自己命名的变量名对应起来就可以了。
具体mdc-demo我放在了github上,地址:
MDC工作原理
看了demo之后,我们再来一起看一下,MDC是如何把traceId放入每行日志中的,这里以logback为例来讲解。
1、先来看一下MDC类自身的结构,MDC类中提供了get,put,remove,clear四个常用的增删改查接口,getCopyOfContextMap方法获取整个线程上下文中存入的Map值,返回的是一个新的Map。可以防止源Map被改动后,影响已获取Map的正常使用。
public static void put(String key, String val) throws IllegalArgumentException
public static String get(String key) throws IllegalArgumentException
public static void remove(String key) throws IllegalArgumentException
public static void clear()
public static Map<String, String> getCopyOfContextMap()
public static void setContextMap(Map<String, String> contextMap)
再来看一下MDC类的初始化,MDC类中持有一个MDCAdapter接口类,在初始化时静态代码块中,会调用bwCompatibleMDCAdapterFromBinder方法,在该方法中再调用StaticMDCBinder的getMDCA()方法,获取MDCAdapter的一个实现。
private static MDCAdapter bwCompatibleGetMDCAdapterFromBinder() throws NoClassDefFoundError {
try {
return StaticMDCBinder.getSingleton().getMDCA();
} catch (NoSuchMethodError nsme) {
// binding is probably a version of SLF4J older than 1.7.14
return StaticMDCBinder.SINGLETON.getMDCA();
}
}
static {
try {
mdcAdapter = bwCompatibleGetMDCAdapterFromBinder();
} catch (NoClassDefFoundError ncde) {
mdcAdapter = new NOPMDCAdapter();
String msg = ncde.getMessage();
if (msg != null && msg.contains("StaticMDCBinder")) {
Util.report("Failed to load class \"org.slf4j.impl.StaticMDCBinder\".");
Util.report("Defaulting to no-operation MDCAdapter implementation.");
Util.report("See " + NO_STATIC_MDC_BINDER_URL + " for further details.");
} else {
throw ncde;
}
} catch (Exception e) {
// we should never get here
Util.report("MDC binding unsuccessful.", e);
}
}
2、MDC类的继承关系
![](https://img.haomeiwen.com/i8749133/1a34912e15d05f11.png)
从上图中可以看出来,MDC类中持有一个MDCAdapter的接口,MDCAdapter接口有三个实现类,分别是BasicMDCAdapter,LogbackMDCAdapter,NOPMDCAdapter这三个类。其中BasicMDCAdapter,NOPMDCAdapter类是SLF4J框架中实现的。LogbackMDCAdapter是logback框架实现的。
下面是MDC初始化时的一段代码,静态代码块中,调用了bwCompatibleGetMDCAdapterFromBinder()这个方法来初始化mdcAdapter接口,
再看bwCompatibleGetMDCAdapterFromBinder()方法的实现,调用了StaticMDCBinder类的getMDCA()方法。StaticMDCBinder类的实现很简单,getMDCA()的方法,直接返回了一个LogbackMDCAdapter的实例。
private static MDCAdapter bwCompatibleGetMDCAdapterFromBinder() throws NoClassDefFoundError {
try {
return StaticMDCBinder.getSingleton().getMDCA();
} catch (NoSuchMethodError nsme) {
// binding is probably a version of SLF4J older than 1.7.14
return StaticMDCBinder.SINGLETON.getMDCA();
}
}
static {
try {
mdcAdapter = bwCompatibleGetMDCAdapterFromBinder();
} catch (NoClassDefFoundError ncde) {
mdcAdapter = new NOPMDCAdapter();
String msg = ncde.getMessage();
if (msg != null && msg.contains("StaticMDCBinder")) {
Util.report("Failed to load class \"org.slf4j.impl.StaticMDCBinder\".");
Util.report("Defaulting to no-operation MDCAdapter implementation.");
Util.report("See " + NO_STATIC_MDC_BINDER_URL + " for further details.");
} else {
throw ncde;
}
} catch (Exception e) {
// we should never get here
Util.report("MDC binding unsuccessful.", e);
}
}
public class StaticMDCBinder {
/**
* The unique instance of this class.
*/
public static final StaticMDCBinder SINGLETON = new StaticMDCBinder();
private StaticMDCBinder() {
}
/**
* Currently this method always returns an instance of
* {@link StaticMDCBinder}.
*/
public MDCAdapter getMDCA() {
return new LogbackMDCAdapter();
}
public String getMDCAdapterClassStr() {
return LogbackMDCAdapter.class.getName();
}
}
3、traceId如何注入到打印的日志中
网友评论