千家信息网

java怎么实现日志追踪MDC

发表于:2025-01-16 作者:千家信息网编辑
千家信息网最后更新 2025年01月16日,这篇文章主要为大家展示了"java怎么实现日志追踪MDC",内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下"java怎么实现日志追踪MDC"这篇文章吧。java
千家信息网最后更新 2025年01月16日java怎么实现日志追踪MDC

这篇文章主要为大家展示了"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 getCopyOfContextMap() {        if (mdcAdapter == null) {            throw new IllegalStateException("MDCAdapter cannot be null. See also " + NULL_MDCA_URL);        }        return mdcAdapter.getCopyOfContextMap();    }      public static void setContextMap(Map 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 配置

                             %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{56} %X{THREAD_ID} - %msg%n                                            

对应的输出日志 可以看到输出了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> copyOnInheritThreadLocal = new InheritableThreadLocal>();

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 local = new ThreadLocal();          work(local);      }     @Test    public void testInheritableThreadLocal() {        final ThreadLocal local = new InheritableThreadLocal();          work(local);    }    private void work(final ThreadLocal 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{ @TestpublicvoidmdcTest1(){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的配置:

3、MDC的使用

@Component@Order(Ordered.HIGHEST_PRECEDENCE)publicclassGlobalLogTagConfigextendsOncePerRequestFilter{privatestaticfinalStringGLOBAL_LOG_TAG="GLOG_TAG"; privatestaticStringgenerateSeqNo(){returnUUID.randomUUID().toString().replace("-","").substring(0,12);} @OverrideprotectedvoiddoFilterInternal(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"这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注行业资讯频道!

线程 日志 内容 方法 输出 信息 功能 变量 标记 用户 结果 局部 流程 篇文章 处理 配置 作用 字符 字符串 属性 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 寻仙2公测后清空数据库 企业信息网络安全等级 打开数据库登陆失败 双cpu服务器必须配电源么 做数据库算不算程序员 沈阳android软件开发流程 内蒙古数据库安全箱服务费 密码学与网络安全第八版答案 数据库中PK_ 深圳市青元网络技术有限公司 服务器必须要设置dns吗 北京点点网络技术有限公司 服务器中的文件在哪里 戴尔服务器管理口登陆名 汕尾幻贩网络技术有限公司 儿童网络安全知识教案 湖北中科网络技术有限公司 湛江计算机网络技术学校 国内bim软件开发商 网络安全手抄报不涂颜色 昆明正规软件开发咨询报价 数据库t4认证是什么 专业工控网络安全解决方案 对网络安全工作造成不良影响 多硬盘服务器装系统 网络安全等级保护备案依据 上海网络安全准入控制价格 服务器的易使用性体现在哪些方面 数据库长连接总查询超时 查询数据库数据关键词
0