Slf4j.MDC源码分析:以及利用MDC和AOP进行日志追踪

本文详细介绍了Slf4j的MDC(Mapped Diagnostic Contexts)功能,用于在日志中添加诊断信息,特别是在线程环境中跟踪请求。通过设置和获取MDC中的键值对,可以在日志输出时加入特定标识,方便在分布式系统中定位操作流程。文章还深入探讨了MDC的源码,分析了其线程局部存储的实现原理,并给出了在实际项目中使用MDC进行日志追踪的实战案例,包括AOP切面的实现和配置。

在 Java 开发中,日志的打印输出是必不可少的,Slf4j + LogBack 的组合是最通用的方式。但是,在分布式系统中,各种无关日志穿行其中,导致我们可能无法直接定位整个操作流程。因此,我们可能需要对一个用户的操作流程进行归类标记,既在其日志信息上添加一个唯一标识,比如使用线程+时间戳,或者用户身份标识等;从大量日志信息中grep出某个用户的操作流程。

MDC ( Mapped Diagnostic Contexts )

  • 顾名思义,其目的是为了便于我们诊断线上问题而出现的方法工具类。虽然,Slf4j 是用来适配其他的日志具体实现包的,但是针对 MDC功能,目前只有logback 以及 log4j 支持。
  • MDC 主要用于保存上下文,区分不同的请求来源。
  • MDC 管理是按线程划分,并且子线程会自动继承母线程的上下文。
MDC使用方式:

一般,我们在代码中,只需要将指定的值put到线程上下文的Map中,然后,在对应的地方使用 get方法获取对应的值。此外,对于一些线程池使用的应用场景,可能我们在最后使用结束时,需要调用clear方法来清洗将要丢弃的数据。

MDC简单使用案例

Tip:此处先举例一个简单的案例,待到对MDC远离分析完毕,再来一个项目实战的案例。

1、在MDC中添加标识:


public class LogTest {
   
   
    private static final Logger logger = LoggerFactory.getLogger(LogTest.class);
 
    public static void main(String[] args) {
   
   
 
        MDC.put("mdc_key", "0000000000001");
        logger.info("这是一条测试日志。");
    }
 
}

2、在logback.xml中配置日志格式:
(关键点在于:traceId:[%X{mdc_trace_id}])

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    
    .....
 
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>[%d{yy-MM-dd.HH:mm:ss.SSS}]  - traceId:[%X{mdc_key}] - %m%n</pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>
</configuration>

3、输出结果:

[18-10-26.10:43:00.281]  - traceId:[0000000000001] - 这是一条测试日志。

MDC源码探索

MDC 的功能实现很简单,就是在线程上下文中,维护一个 Map<String,String> 属性来支持日志输出的时候,当我们在配置文件logback.xml 中配置了%X{key},则后台日志打印出对应的 key 的值。

其实对于MDC,可以简单将其理解为一个线程级的容器。对于标识的操作其实也很简单,大部分就是put、get和clear

先来看看 org.slf4j.MDC

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package org.slf4j;

import java.io.Closeable;
import java.util.Map;
import org.slf4j.helpers.NOPMDCAdapter;
import org.slf4j.helpers.Util;
import org.slf4j.impl.StaticMDCBinder;
import org.slf4j.spi.MDCAdapter;

public class MDC {
   
   
    
    static MDCAdapter mdcAdapter;

    除了put\get\remove\clear外,其他方法省略....

    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 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();
        }
    }


    static {
   
   
        try {
   
   
            mdcAdapter = StaticMDCBinder.SINGLETON.getMDCA();
        } catch (NoClassDefFoundError var2) {
   
   
            mdcAdapter = new NOPMDCAdapter();
            String msg = var2.getMessage();
            if (msg == null || msg.indexOf("StaticMDCBinder") == -1) {
   
   
                throw var2;
            }

            Util.report("Failed to load class \"org.slf4j.impl.StaticMDCBinder\".")</
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值