温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

java怎么实现日志追踪MDC

发布时间:2021-09-24 10:52:34 来源:亿速云 阅读:334 作者:小新 栏目:开发技术

这篇文章主要为大家展示了“java怎么实现日志追踪MDC”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“java怎么实现日志追踪MDC”这篇文章吧。

java 日志追踪MDC

MDC ( Mapped Diagnostic Contexts ) 有了日志之后,我们就可以追踪各种线上问题。

但是,在分布式系统中,各种无关日志穿行其中,导致我们可能无法直接定位整个操作流程。

因此,我们可能需要对一个用户的操作流程进行归类标记,比如使用线程+时间戳,或者用户身份标识等;如此,我们可以从大量日志信息中grep出某个用户的操作流程,或者某个时间的流转记录。其目的是为了便于我们诊断线上问题而出现的方法工具类。

虽然,Slf4j 是用来适配其他的日志具体实现包的,但是针对 MDC功能,目前只有logback 以及 log4j 支持。 MDC

package org.slf4j;  import java.io.Closeable; import java.util.Map;  import org.slf4j.helpers.NOPMDCAdapter; import org.slf4j.helpers.BasicMDCAdapter; import org.slf4j.helpers.Util; import org.slf4j.impl.StaticMDCBinder; import org.slf4j.spi.MDCAdapter;  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;        public static class MDCCloseable implements Closeable {         private final String key;          private MDCCloseable(String key) {             this.key = key;         }          public void close() {             MDC.remove(this.key);         }     }      private MDC() {     }       static {         try {             mdcAdapter = StaticMDCBinder.SINGLETON.getMDCA();         } catch (NoClassDefFoundError ncde) {             mdcAdapter = new NOPMDCAdapter();             String msg = ncde.getMessage();             if (msg != null && msg.indexOf("StaticMDCBinder") != -1) {                 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 static void put(String key, String val) throws IllegalArgumentException {         if (key == null) {             throw new IllegalArgumentException("key parameter cannot be null");         }         if (mdcAdapter == null) {             throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL);         }         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");         }           if (mdcAdapter == null) {             throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL);         }         return mdcAdapter.get(key);     }       public static void remove(String key) throws IllegalArgumentException {         if (key == null) {             throw new IllegalArgumentException("key parameter cannot be null");         }           if (mdcAdapter == null) {             throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL);         }         mdcAdapter.remove(key);     }        public static void clear() {         if (mdcAdapter == null) {             throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL);         }         mdcAdapter.clear();     }       public static Map<String, String> getCopyOfContextMap() {         if (mdcAdapter == null) {             throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL);         }         return mdcAdapter.getCopyOfContextMap();     }        public static void setContextMap(Map<String, String> contextMap) {         if (mdcAdapter == null) {             throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL);         }         mdcAdapter.setContextMap(contextMap);     }      public static MDCAdapter getMDCAdapter() {         return mdcAdapter;     }  }

简单的demo

package com.alibaba.otter.canal.common;  import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.MDC;  public class LogTest {     private static final Logger logger = LoggerFactory.getLogger(LogTest.class);     public static void main(String[] args) {         MDC.put("THREAD_ID", String.valueOf(Thread.currentThread().getId()));         logger.info("纯字符串信息的info级别日志");     } }

logback.xml 配置

<configuration scan="true" scanPeriod=" 5 seconds">       <jmxConfigurator />     <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">         <encoder>             <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} %X{THREAD_ID} - %msg%n             </pattern>         </encoder>     </appender>          <root level="INFO">         <appender-ref ref="STDOUT"/>     </root> </configuration

对应的输出日志 可以看到输出了THREAD_ID

2016-12-08 14:59:32.855 [main] INFO com.alibaba.otter.canal.common.LogTest THREAD_ID 1 - 纯字符串信息的info级别日志

slf4j只是起到适配的作用 故查看实现类LogbackMDCAdapter属性

final InheritableThreadLocal<Map<String, String>> copyOnInheritThreadLocal = new InheritableThreadLocal<Map<String, String>>();

InheritableThreadLocal 该类扩展了 ThreadLocal,为子线程提供从父线程那里继承的值:在创建子线程时,子线程会接收所有

可继承的线程局部变量的初始值,以获得父线程所具有的值。通常,子线程的值与父线程的值是一致的;但是,通过重写这个类中的 childValue 方法,子线程的值可以作为父线程值的一个任意函数。

当必须将变量(如用户 ID 和 事务 ID)中维护的每线程属性(per-thread-attribute)自动传送给创建的所有子线程时,应尽可能地采用可继承的线程局部变量,而不是采用普通的线程局部变量

验证一下

package com.alibaba.otter.canal.parse.driver.mysql;  import org.junit.Test;  public class TestInheritableThreadLocal {     @Test     public void testThreadLocal() {         final ThreadLocal<String> local = new ThreadLocal<String>();           work(local);       }       @Test     public void testInheritableThreadLocal() {         final ThreadLocal<String> local = new InheritableThreadLocal<String>();           work(local);     }     private void work(final ThreadLocal<String> local) {           local.set("a");           System.out.println(Thread.currentThread() + "," + local.get());           Thread t = new Thread(new Runnable() {                              @Override               public void run() {                   System.out.println(Thread.currentThread() + "," + local.get());                   local.set("b");                   System.out.println(Thread.currentThread() + "," + local.get());               }           });                      t.start();           try {               t.join();           } catch (InterruptedException e) {               e.printStackTrace();           }                     System.out.println(Thread.currentThread() + "," + local.get());       }   }

分别运行得到的输出结果

ThreadLocal 存贮输出结果
Thread[main,5,main],a
Thread[Thread-0,5,main],null
Thread[Thread-0,5,main],b
Thread[main,5,main],a

InheritableThreadLocal存贮输出结果
Thread[main,5,main],a
Thread[Thread-0,5,main],a
Thread[Thread-0,5,main],b
Thread[main,5,main],a

输出结果说明一切 对于参数传递十分有用 我知道 canal的源码中用到了MDC

在 CanalServerWithEmbedded 中的 start 和stop等方法中都有用到

public void start(final String destination) {         final CanalInstance canalInstance = canalInstances.get(destination);         if (!canalInstance.isStart()) {             try {                 MDC.put("destination", destination);                 canalInstance.start();                 logger.info("start CanalInstances[{}] successfully", destination);             } finally {                 MDC.remove("destination");             }         }     }       public void stop(String destination) {         CanalInstance canalInstance = canalInstances.remove(destination);         if (canalInstance != null) {             if (canalInstance.isStart()) {                 try {                     MDC.put("destination", destination);                     canalInstance.stop();                     logger.info("stop CanalInstances[{}] successfully", destination);                 } finally {                     MDC.remove("destination");                 }             }         }     }

MDC的介绍及使用

1、MDC是什么?

MDC是(Mapped Diagnostic Context,映射调试上下文)是 log4j 和 logback 支持的一种方便在多线程条件下记录追踪日志的功能。通常打印出的日志会有线程号等信息来标志当前日志属于哪个线程,然而由于线程是可以重复使用的,所以并不能很清晰的确认一个请求的日志范围。处理这种情况一般有两种处理方式:

1)手动生成一个唯一序列号打印在日志中;

2)使用日志控件提供的MDC功能,生成一个唯一序列标记一个线程的日志;

两种方法的区别在于:

方法一只能标记一条日志,线程内其他日志需要人肉去筛选;

方法二标记整个线程的所有日志,方便grep命令查询;

对比可见,使用MDC功能更好。

2、MDC的原理

MDC 可以看成是一个与当前线程绑定的哈希表,可以往其中添加键值对。MDC 中包含的内容可以被同一线程中执行的代码所访问。当前线程的子线程会继承其父线程中的 MDC 的内容。当需要记录日志时,只需要从 MDC 中获取所需的信息即可。MDC 的内容则由程序在适当的时候保存进去。对于一个 Web 应用来说,通常是在请求被处理的最开始保存这些数据。

@RunWith(SpringRunner.class) @SpringBootTest(classes=CreditAppApplication.class) publicclassMDCTest{   @Test publicvoidmdcTest1(){ MDC.put("first","thefirst1");   Loggerlogger=LoggerFactory.getLogger(MDCTest.class); MDC.put("last","thelast1");   logger.info("checkenclosed."); logger.debug("themostbeautifultwowordsinenglish.");   MDC.put("first","thefirst2"); MDC.put("last","thelast2");   logger.info("iamnotacrook."); logger.info("AttributedtotheformerUSpresident.17Nov1973."); } }

logback的配置:

java怎么实现日志追踪MDC

3、MDC的使用

@Component @Order(Ordered.HIGHEST_PRECEDENCE) publicclassGlobalLogTagConfigextendsOncePerRequestFilter{ privatestaticfinalStringGLOBAL_LOG_TAG="GLOG_TAG";  privatestaticStringgenerateSeqNo(){ returnUUID.randomUUID().toString().replace("-","").substring(0,12); }   @Override protectedvoiddoFilterInternal(HttpServletRequesthttpServletRequest,HttpServletResponsehttpServletResponse,FilterChainfilterChain)throwsServletException,IOException{ try{ StringseqNo; if(httpServletRequest!=null){ seqNo=httpServletRequest.getHeader(GLOBAL_LOG_TAG);   if(StringUtils.isEmpty(seqNo)){ seqNo=generateSeqNo(); } }else{ seqNo=generateSeqNo(); } MDC.put(GLOBAL_LOG_TAG,seqNo); filterChain.doFilter(httpServletRequest,httpServletResponse); }finally{ MDC.remove(GLOBAL_LOG_TAG); } } }

注意:

OncePerRequestFilter的作用是为了让每个请求只经过这个过滤器一次(因为web container的不同,有些过滤器可能被多次执行)

logback配置:

java怎么实现日志追踪MDC

以上是“java怎么实现日志追踪MDC”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI