Logback+Spring-Aop实现全面生态化的全链路日志追踪系统服务插件「Logback-MDC篇」
Posted 浩宇の天尚
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Logback+Spring-Aop实现全面生态化的全链路日志追踪系统服务插件「Logback-MDC篇」相关的知识,希望对你有一定的参考价值。
日志追踪
日志追踪对于功能问题的排查和数据流转的路径分析时非常重要的,有了全链路日志追踪体系机制可以非常有效且快速的定位问题,但在多线程环境中,若没有相关成熟的框架的支持,想要实现日志追踪,就需要手动将主线程中的日志参数传递给子线程,接下来就在线程池场景下借助MDC实现了traceId参数的透传。
候选方案
-
方案1:解决办法是采用自定义的日志格式,把用户的信息采用某种方式编码在日志记录中。这种方式的问题在于要求在每个使用日志记录器的类中,都可以访问到用户相关的信息。这样才可能在记录日志时使用。这样的条件通常是比较难以满足的,MDC的作用是解决这个问题。
-
方案2: MDC(Mapped Diagnostic Context,映射调试上下文) 是slf4j提供的一种轻量级的日志跟踪工具,Log4j、Logback或者Log4j2等日志中最常见区分同一个请求的方式是通过线程名/线程ID,但是而如果请求量大,线程名/线程ID会在相邻的时间内出现多次重复的打印,因此引出了trace-id,即在接收到的时候生成唯一的请求id,在整个执行链路中带上此唯一id。
Sl4fj的MDC模式的介绍
Sl4fj的MDC的源码
public class MDC
static final String NULL_MDCA_URL = "http://www.slf4j.org/codes.html#null_MDCA";
static final String NO_STATIC_MDC_BINDER_URL = "http://www.slf4j.org/codes.html#no_static_mdc_binder";
static MDCAdapter mdcAdapter;
private MDC()
private static MDCAdapter bwCompatibleGetMDCAdapterFromBinder() throws NoClassDefFoundError
try
return StaticMDCBinder.getSingleton().getMDCA();
catch (NoSuchMethodError var1)
return StaticMDCBinder.SINGLETON.getMDCA();
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 MDCCloseable putCloseable(String key, String val) throws IllegalArgumentException
put(key, val);
return new MDCCloseable(key);
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();
public static Map<String, String> getCopyOfContextMap()
if (mdcAdapter == null)
throw new IllegalStateException("MDCAdapter cannot be null. See also http://www.slf4j.org/codes.html#null_MDCA");
else
return mdcAdapter.getCopyOfContextMap();
public static void setContextMap(Map<String, String> contextMap)
if (mdcAdapter == null)
throw new IllegalStateException("MDCAdapter cannot be null. See also http://www.slf4j.org/codes.html#null_MDCA");
else
mdcAdapter.setContextMap(contextMap);
public static MDCAdapter getMDCAdapter()
return mdcAdapter;
static
try
mdcAdapter = bwCompatibleGetMDCAdapterFromBinder();
catch (NoClassDefFoundError var2)
mdcAdapter = new NOPMDCAdapter();
String msg = var2.getMessage();
if (msg == null || !msg.contains("StaticMDCBinder"))
throw var2;
Util.report("Failed to load class \\"org.slf4j.impl.StaticMDCBinder\\".");
Util.report("Defaulting to no-operation MDCAdapter implementation.");
Util.report("See http://www.slf4j.org/codes.html#no_static_mdc_binder for further details.");
catch (Exception var3)
Util.report("MDC binding unsuccessful.", var3);
public static class MDCCloseable implements Closeable
private final String key;
private MDCCloseable(String key)
this.key = key;
public void close()
MDC.remove(this.key);
Sl4fj的MDC模式主要的门面类是MDC.java,但是最核心的类是MDCAdapter,可由下面的代码观测出。
对应而定实现加载所有相关的MDCAdapter的类就在这里,而代码里面的功能展示信息也是我们经常会遇见的,如果出现错误的时候,经常会打印再日志的最开始部分。
Sl4fj的MDCAdapter的源码
大家可以观察到对应的MDCAdapter的实现类有一下这几种,基本上是会交由第三方去实现的。
而对于Sl4j本身不提供传递traceId的能力,真正提供能力的是MDCAdapter接口的实现。
Logback使用的是LogbackMDCAdapter。比如Log4j的是Log4jMDCAdapter,Logback的是LogbackMDCAdapter。其内部自己的MDCAdapter属于空实现的NOPMDCAdapter类和BasicMDCAdapter,第三方的OPMDCAdapter,不太了解。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Sw9tGguI-1668663492640)(https://oscimg.oschina.net/oscnet/up-50011a2da52c96dfaf890a62f1e6b268341.png)]
MDC的实现原理
- MDC可以看成是每个线程内部都进行顶一个所属私有的容器(也可以理解为哈希表),我们可以通过程序往其中添加键值对。
- MDC内的数据可以被当前线程的代码所访问,当前线程所产生的子线程也会继承其父线程中的MDC的数据,当需要记录日志时,只需要从MDC中获取所需的信息即可。
- MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。
Sl4j的MDCAdapter的实现机制
public class BasicMDCAdapter implements MDCAdapter
private InheritableThreadLocal<Map<String, String>> inheritableThreadLocal = new InheritableThreadLocal<Map<String, String>>()
protected Map<String, String> childValue(Map<String, String> parentValue)
return parentValue == null ? null : new HashMap(parentValue);
;
public BasicMDCAdapter()
public void put(String key, String val)
if (key == null)
throw new IllegalArgumentException("key cannot be null");
else
Map<String, String> map = (Map)this.inheritableThreadLocal.get();
if (map == null)
map = new HashMap();
this.inheritableThreadLocal.set(map);
((Map)map).put(key, val);
public String get(String key)
Map<String, String> map = (Map)this.inheritableThreadLocal.get();
return map != null && key != null ? (String)map.get(key) : null;
public void remove(String key)
Map<String, String> map = (Map)this.inheritableThreadLocal.get();
if (map != null)
map.remove(key);
public void clear()
Map<String, String> map = (Map)this.inheritableThreadLocal.get();
if (map != null)
map.clear();
this.inheritableThreadLocal.remove();
public Set<String> getKeys()
Map<String, String> map = (Map)this.inheritableThreadLocal.get();
return map != null ? map.keySet() : null;
public Map<String, String> getCopyOfContextMap()
Map<String, String> oldMap = (Map)this.inheritableThreadLocal.get();
return oldMap != null ? new HashMap(oldMap) : null;
public void setContextMap(Map<String, String> contextMap)
this.inheritableThreadLocal.set(new HashMap(contextMap));
Logback的MDC模式
- LogbackMDCAdapter类实现MDCAdapter接口,实现 put、get、remove等方法。
- copyOnThreadLocal:存储每个线程的多个变量
当在logback.xml中配置了%Xkey 或 SiftingAppender的的,在需要输出日志的时候,从MDC中获取对应的key值,然后append到日志字符串中或生成文件路径,然后输出。
LogbackMDCAdapter的源码分析
public class LogbackMDCAdapter implements MDCAdapter
final ThreadLocal<Map<String, String>> copyOnThreadLocal = new ThreadLocal();
private static final int WRITE_OPERATION = 1;
private static final int MAP_COPY_OPERATION = 2;
final ThreadLocal<Integer> lastOperation = new ThreadLocal();
public LogbackMDCAdapter()
private Integer getAndSetLastOperation(int op)
Integer lastOp = (Integer)this.lastOperation.get();
this.lastOperation.set(op);
return lastOp;
private boolean wasLastOpReadOrNull(Integer lastOp)
return lastOp == null || lastOp == 2;
private Map<String, String> duplicateAndInsertNewMap(Map<String, String> oldMap)
Map<String, String> newMap = Collections.synchronizedMap(new HashMap());
if (oldMap != null)
synchronized(oldMap)
newMap.putAll(oldMap);
this.copyOnThreadLocal.set(newMap);
return newMap;
public void put(String key, String val) throws IllegalArgumentException
if (key == null)
throw new IllegalArgumentException("key cannot be null");
else
Map<String, String> oldMap = (Map)this.copyOnThreadLocal.get();
Integer lastOp = this.getAndSetLastOperation(1);
if (!this.wasLastOpReadOrNull(lastOp) && oldMap != null)
oldMap.put(key, val);
else
Map<String, String> newMap = this.duplicateAndInsertNewMap(oldMap);
newMap.put(key, val);
public void remove(String key)
if (key != null)
Map<String, String> oldMap = (Map)this.copyOnThreadLocal.get();
if (oldMap != null)
Integer lastOp = this.getAndSetLastOperation(1);
if (this.wasLastOpReadOrNull(lastOp))
Map<String, String> newMap = this.duplicateAndInsertNewMap(oldMap);
newMap.remove(key);
else
oldMap.remove(key);
public void clear()
this.lastOperation.set(1);
this.copyOnThreadLocal.remove();
public String get(String key)
Map<String, String> map = (Map)this.copyOnThreadLocal.get();
return map != null && key != null ? (String)map.get(key) : null;
public Map<String, String> getPropertyMap()
this.lastOperation.set(2);
return (Map)this.copyOnThreadLocal.get();
public Set<String> getKeys()
Map<String, String> map = this.getPropertyMap();
return map != null ? map.keySet() : null;
public Map<String, String> getCopyOfContextMap()
Map<String, String以上是关于Logback+Spring-Aop实现全面生态化的全链路日志追踪系统服务插件「Logback-MDC篇」的主要内容,如果未能解决你的问题,请参考以下文章
Logback+Spring-Aop实现全面生态化的全链路日志追踪系统服务插件「Logback-MDC篇」