在 Java 开发中,日志的打印输出是必不可少的,Slf4j + LogBack
的组合是最通用的方式。但是,在分布式系统中,各种无关日志穿行其中,导致我们可能无法直接定位整个操作流程。因此,我们可能需要对一个用户的操作流程进行归类标记,既在其日志信息上添加一个唯一标识,比如使用线程+时间戳,或者用户身份标识等;从大量日志信息中grep出某个用户的操作流程。
MDC ( Mapped Diagnostic Contexts )
- 顾名思义,其目的是为了便于我们诊断线上问题而出现的方法工具类。虽然,Slf4j 是用来适配其他的日志具体实现包的,但是针对 MDC功能,目前只有logback 以及 log4j 支持。
- MDC 主要用于保存上下文,区分不同的请求来源。
- MDC 管理是按线程划分,并且子线程会自动继承母线程的上下文。
MDC使用方式:
一般,我们在代码中,只需要将指定的值put
到线程上下文的Map中,然后,在对应的地方使用 get
方法获取对应的值。此外,对于一些线程池使用的应用场景,可能我们在最后使用结束时,需要调用clear
方法来清洗将要丢弃的数据。
MDC简单使用案例
Tip:此处先举例一个简单的案例,待到对MDC远离分析完毕,再来一个项目实战的案例。
1、在MDC中添加标识:
public class LogTest {
private static final Logger logger = LoggerFactory.getLogger(LogTest.class);
public static void main(String[] args) {
MDC.put("mdc_key", "0000000000001");
logger.info("这是一条测试日志。");
}
}
2、在logback.xml中配置日志格式:
(关键点在于:traceId:[%X{mdc_trace_id}])
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
.....
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>[%d{yy-MM-dd.HH:mm:ss.SSS}] - traceId:[%X{mdc_key}] - %m%n</pattern>
<charset>UTF-8</charset>
</encoder>
</appender>
</configuration>
3、输出结果:
[18-10-26.10:43:00.281] - traceId:[0000000000001] - 这是一条测试日志。
MDC源码探索
MDC 的功能实现很简单,就是在线程上下文中,维护一个 Map<String,String>
属性来支持日志输出的时候,当我们在配置文件logback.xml
中配置了%X{key}
,则后台日志打印出对应的 key
的值。
其实对于MDC,可以简单将其理解为一个线程级的容器。对于标识的操作其实也很简单,大部分就是put、get和clear
。
先来看看 org.slf4j.MDC
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//
package org.slf4j;
import java.io.Closeable;
import java.util.Map;
import org.slf4j.helpers.NOPMDCAdapter;
import org.slf4j.helpers.Util;
import org.slf4j.impl.StaticMDCBinder;
import org.slf4j.spi.MDCAdapter;
public class MDC {
static MDCAdapter mdcAdapter;
除了put\get\remove\clear外,其他方法省略....
public static void put(String key, String val) throws IllegalArgumentException {
if (key == null) {
throw new IllegalArgumentException("key parameter cannot be null");
} else if (mdcAdapter == null) {
throw new IllegalStateException("MDCAdapter cannot be null. See also http://www.slf4j.org/codes.html#null_MDCA");
} else {
mdcAdapter.put(key, val);
}
}
public static String get(String key) throws IllegalArgumentException {
if (key == null) {
throw new IllegalArgumentException("key parameter cannot be null");
} else if (mdcAdapter == null) {
throw new IllegalStateException("MDCAdapter cannot be null. See also http://www.slf4j.org/codes.html#null_MDCA");
} else {
return mdcAdapter.get(key);
}
}
public static void remove(String key) throws IllegalArgumentException {
if (key == null) {
throw new IllegalArgumentException("key parameter cannot be null");
} else if (mdcAdapter == null) {
throw new IllegalStateException("MDCAdapter cannot be null. See also http://www.slf4j.org/codes.html#null_MDCA");
} else {
mdcAdapter.remove(key);
}
}
public static void clear() {
if (mdcAdapter == null) {
throw new IllegalStateException("MDCAdapter cannot be null. See also http://www.slf4j.org/codes.html#null_MDCA");
} else {
mdcAdapter.clear();
}
}
static {
try {
mdcAdapter = StaticMDCBinder.SINGLETON.getMDCA();
} catch (NoClassDefFoundError var2) {
mdcAdapter = new NOPMDCAdapter();
String msg = var2.getMessage();
if (msg == null || msg.indexOf("StaticMDCBinder") == -1) {
throw var2;
}
Util.report("Failed to load class \"org.slf4j.impl.StaticMDCBinder\".")</