Logback+Spring-Aop实现全面生态化的全链路日志追踪系统服务插件「Logback-MDC篇」

Posted 浩宇の天尚

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Logback+Spring-Aop实现全面生态化的全链路日志追踪系统服务插件「Logback-MDC篇」相关的知识,希望对你有一定的参考价值。

日志追踪

日志追踪对于功能问题的排查和数据流转的路径分析时非常重要的,有了全链路日志追踪体系机制可以非常有效且快速的定位问题,但在多线程环境中,若没有相关成熟的框架的支持,想要实现日志追踪,就需要手动将主线程中的日志参数传递给子线程,接下来就在线程池场景下借助MDC实现了traceId参数的透传。

候选方案

  • 方案1:解决办法是采用自定义的日志格式,把用户的信息采用某种方式编码在日志记录中。这种方式的问题在于要求在每个使用日志记录器的类中,都可以访问到用户相关的信息。这样才可能在记录日志时使用。这样的条件通常是比较难以满足的,MDC的作用是解决这个问题

  • 方案2: MDC(Mapped Diagnostic Context,映射调试上下文) 是slf4j提供的一种轻量级的日志跟踪工具,Log4jLogback或者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的实现原理

  1. MDC可以看成是每个线程内部都进行顶一个所属私有的容器(也可以理解为哈希表),我们可以通过程序往其中添加键值对。
  2. MDC内的数据可以被当前线程的代码所访问,当前线程所产生的子线程也会继承其父线程中的MDC的数据,当需要记录日志时,只需要从MDC中获取所需的信息即可。
  3. 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篇」

源码详解系列 ------ 全面讲解logback的使用和源码

注解实现的spring-aop

深入理解spring-AOP注解的底层实现原理

xml实现的spring-aop

Spring-AOP实现