MDC的快速入门

  • Post author:
  • Post category:其他


这两天看到别人写了几行代码不是很理解,

MDC.put("dajiahao", dajihao);       
......
MDC.remove("dajiahao");

于是就去学了一下,这是我大概的总结

MDC 全称是 Mapped Diagnostic Context,是 log4j 和


logback


提供的一种方便在多线程条件下记录日志的功能,也可以说是一种轻量级的日志跟踪工具。

就是可以看到你想知道的日志内容且不需要打印出来。



MDC的基本定义

package org.example.MDC;

/**
 * @program: lx-stream
 * @description:
 * @packagename: org.example.MDC
 * @date: 2023-02-22 20:07
 **/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.UUID;
public class Demo {
    private static final Logger logger = LoggerFactory.getLogger(Demo.class);
    public static final String REQ_ID = "REQ_ID";

    public static void main(String[] args) {
//当 logback 内置的日志字段不能满足业务需求时,便可以借助 MDC 机制,将业务上想要输出的信息,通过 logback 给打印出来;
        MDC.put(REQ_ID, UUID.randomUUID().toSring());// 随机生成id作为标识  来记录你想查看的日志,

        logger.info("开始,进行业务处理");
        logger.info("业务处理完毕,可以释放空间了,避免内存泄露");
//当调用 MDC.remove(Key) 后,便可将业务字段从 MDC 中删除,日志中就不再打印请求 ID 
        MDC.remove(REQ_ID);   //删除id
        logger.info("REQ_ID 还有吗?{}", MDC.get(REQ_ID) != null);
    }
}



控制台输出:
[main] [d28dd663-4b21-401a-9cf1-df501ac1926e] - 开始,进行业务处理
[main] [d28dd663-4b21-401a-9cf1-df501ac1926e] - 业务处理完毕,可以释放空间了,避免内存泄露
[main] [] - REQ_ID 还有吗?false

接下来配置 logback.xml,通过 %X{REQ_ID} 来打印 REQ_ID 的信息,logback.xml 文件内容如下。

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <Pattern>[%t] [%X{REQ_ID}] - %m%n</Pattern>
        </layout>
    </appender>
    <root level="debug">
        <appender-ref ref="CONSOLE"/>
    </root>
</configuration>

依赖包

<dependencies>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.7.7</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-core</artifactId>
        <version>1.2.3</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-access</artifactId>
        <version>1.2.3</version>
    </dependency>
    <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
    </dependency>
</dependencies>


a)MDC 提供的 put 方法,可以将一个 K-V 的键值对放到容器中,并且能保证同一个线程内,Key 是唯一的,不同的线程 MDC 的值互不影响;


b) 在 logback.xml 中,在 layout 中可以通过声明 %X{REQ_ID} 来输出 MDC 中 REQ_ID 的信息;


c)MDC 提供的 remove 方法,可以清除 MDC 中指定 key 对应的键值对信息。



在多线程情况下,MDC发生的变化

package org.example.MDC;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import java.util.UUID;

public class MDCA {
    public static void main(String[] args) {
        new BizHandle("A").start();
        new BizHandle("B").start();
        new BizHandle("C").start();
    }
}

class BizHandle extends Thread {

    private static final Logger logger = LoggerFactory.getLogger(MDCA.class);
    public static final String REQ_ID = "REQ_ID";

    private String funCode;

    public BizHandle(String funCode) {
        this.funCode = funCode;
    }

    @Override
    public void run() {
        MDC.put(REQ_ID, UUID.randomUUID().toString());
        logger.info("开始调用服务{},进行业务处理", funCode);
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            logger.info(e.getMessage());
        }
        logger.info("服务{}处理完毕,可以释放空间了,避免内存泄露", funCode);
        MDC.remove(REQ_ID);
    }
}


控制台输出
[Thread-0] [83a388ea-36f4-4588-b888-7c419fa381ce] - 开始调用服务A,进行业务处理
[Thread-2] [f04b57aa-c911-4341-8376-50a9c062a5d3] - 开始调用服务C,进行业务处理
[Thread-1] [40bd47d5-d92b-43e3-b998-443cdb5294d3] - 开始调用服务B,进行业务处理
[Thread-2] [f04b57aa-c911-4341-8376-50a9c062a5d3] - 服务C处理完毕,可以释放空间了,避免内存泄露
[Thread-1] [40bd47d5-d92b-43e3-b998-443cdb5294d3] - 服务B处理完毕,可以释放空间了,避免内存泄露
[Thread-0] [83a388ea-36f4-4588-b888-7c419fa381ce] - 服务A处理完毕,可以释放空间了,避免内存泄露

线程 Thread-0 与 Thread-1 在 MDC 中放入的 REQ_ID 的值是互不影响,也就是说 MDC 中的值是与线程绑定在一起的。



MDC的底层


首先通过 org.slf4j.MDC 的源码,可以很清楚的知道 MDC 主要是通过 MDCAdapter 来完成 put、get、remove 等操作。


不出所料 MDCAdapter 也是个接口。在 Java 的世界里,应该都知道定义接口的目的:就是为了定义规范,让子类去实现。


MDCAdapter 就和 JDBC 的规范类似,专门用于定义操作规范。JDBC 是为了定义数据库操作规范,让数据库厂商(MySQL、DB2、Oracle 等)去实现;而 MDCAdapter 则是让具体的日志组件(logback、log4j等)去实现。


MDCAdapter 接口的实现类,有 NOPMDCAdapter、BasicMDCAdapter、LogbackMDCAdapter 以及 Log4jMDCAdapter 等等几种,其中 log4j 使用的是 Log4jMDCAdapter,而 Logback 使用的是 LogbackMDCAdapter。


本次重点说 LogbackMDCAdapter 的源码,截图如下。


MDC 底层最终使用的是 ThreadLocal 来进行的实现。


a)ThreadLocal 很多地方叫做线程本地变量,也有些地方叫做线程本地存储。


b)ThreadLocal 的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。


c)ThreadLocal 使用场景为用来解决数据库连接、Session 管理等。



MDC 的应用场景:


a)在 WEB 应用中,如果想在日志中输出请求用户 IP 地址、请求 URL、统计耗时等等,MDC 基本都能支撑;


b)在 WEB 应用中,如果能画出用户的请求到响应整个过程,势必会快速定位生产问题,那么借助 MDC 来保存用户请求时产生的 reqId,当请求完成后,再将这个 reqId 进行移除,这么一来通过 grep reqId 就能轻松 get 整个请求流程的日志轨迹;


c)在微服务盛行的当下,链路跟踪是个难题,而借助 MDC 去埋点,巧妙实现链路跟踪应该不是问题。



https://zhuanlan.zhihu.com/p/469070410



版权声明:本文为m0_53660431原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。