MDC 实现全链路调用日志跟踪

文章介绍了在多线程环境下MDC(MappedDiagnosticContext)信息丢失的问题,以及如何通过自定义线程池和装饰器模式来确保MDC在子线程中正确传播,从而实现日志追踪。示例代码展示了如何创建一个`MDCThreadPoolExecutorTask`类,用于包装Spring的`ThreadPoolTaskExecutor`,并在`submit`方法中传递MDC上下文。此外,还提供了一个`MDCRunnable`装饰器来保持MDC在异步线程中的完整性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MDC 线程池丢失

1、自定义线程池任务继承ThreadPoolTaskExecutor 并重写

import com.google.common.base.Strings;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.MapUtils;
import org.slf4j.MDC;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;

/**
 * 装饰ThreadPoolExecutor,将父线程的MDC内容传给子线程
 */
@Slf4j
public class MDCThreadPoolExecutorTask extends ThreadPoolTaskExecutor {
    private final static String TRACE_ID = "traceId";
    private static final long serialVersionUID = 1L;

    @Override
    public <T> Future<T> submit(Callable<T> task) {
        Map<String, String> context = MDC.getCopyOfContextMap();
        return super.submit(() -> {
            T result = null;
            try {
                if (MapUtils.isNotEmpty(context) && !Strings.isNullOrEmpty(context.get(TRACE_ID))) {
                    MDC.setContextMap(context);
                } else {
                    LogTraceIdAop.setMDC();
                }
            }catch (Exception e){

            }
            try {
                result = task.call();
            } finally {
                try {
                    LogTraceIdAop.clear();
                } catch (Exception e) {
                    log.warn("mdc clear exception.", e);
                }
            }
            return result;
        });
    }

}

2、使用自定义线程池-springboot

import com.fintell.dp3.common.config.ComConfig;
import com.fintell.dp3.common.log.MDCThreadPoolExecutorTask;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * 线程池配置
 */
@Configuration
@EnableAsync
@Slf4j
public class ThreadPoolTaskConfig {
	/** 
	 *   默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,
	 *	当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
	 *  当队列满了,就继续创建线程,当线程数量大于等于maxPoolSize后,开始使用拒绝策略拒绝 
	 */
	// 核心线程数(默认线程数)
	@Value("${thread.corePoolSize:0}")
	private int corePoolSize;
	// 最大线程数
	@Value("${thread.maxPoolSize:100}")
	private int maxPoolSize;
	// 缓冲队列大小
	@Value("${thread.queueCapacity:200}")
	private int queueCapacity;
	// 允许线程空闲时间(单位:默认为秒)
	@Value("${thread.keepAliveTime:10}")
	private int keepAliveTime;
	/** 线程池名前缀 */
	private static final String threadNamePrefix = "AsyncTask-Api-";

	/**
	 * 核心处理逻辑线程池
	 * 1、查询待加工变量列表
	 * 2、变量加工
	 * 3、发送消息队列
	 */
	@Bean("taskExecutor")
	public ThreadPoolTaskExecutor taskExecutor() {
		corePoolSize = (corePoolSize == 0 ? ComConfig.getThreads() : corePoolSize);
		// 使用自定义线程池
		MDCThreadPoolExecutorTask executor = new MDCThreadPoolExecutorTask();
		executor.setCorePoolSize(corePoolSize);
		executor.setMaxPoolSize(maxPoolSize);
		executor.setQueueCapacity(queueCapacity);
		executor.setKeepAliveSeconds(keepAliveTime);
		executor.setThreadNamePrefix(threadNamePrefix + "core");
		log.info("ThreadPoolTaskExecutor - corePoolSize:{}. maxPoolSize:{}. queueCapacity{}. keepAliveTime{}. threadNamePrefix{}.",
				corePoolSize, maxPoolSize, queueCapacity, keepAliveTime, threadNamePrefix + "core");
		// 线程池对拒绝任务的处理策略
		// CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务
		executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
		// 初始化
		executor.initialize();
		return executor;
	}
	
}

MDC多线程丢失

1、自定义线程

import org.slf4j.MDC;

import java.util.Map;

/**
 * 装饰器模式装饰Runnable,传递父线程的线程号
 *
 * @author yangyongjie
 * @date 2020/3/9
 * @desc
 */
public class MDCRunnable implements Runnable {

    private Runnable runnable;

    /**
     * 保存当前主线程的MDC值
     */
    private final Map<String, String> mainMdcMap;

    public MDCRunnable(Runnable runnable) {
        this.runnable = runnable;
        this.mainMdcMap = MDC.getCopyOfContextMap();
    }

    @Override
    public void run() {
        // 将父线程的MDC值赋给子线程
        for (Map.Entry<String, String> entry : mainMdcMap.entrySet()) {
            MDC.put(entry.getKey(), entry.getValue());
        }
        // 执行被装饰的线程run方法
        runnable.run();
        // 执行结束移除MDC值
        for (Map.Entry<String, String> entry : mainMdcMap.entrySet()) {
            MDC.put(entry.getKey(), entry.getValue());
        }
    }

}

2、使用自定义线程

// 异步线程打印日志,用MDCRunnable装饰Runnable
        new Thread(new MDCRunnable(new Runnable() {
            @Override
            public void run() {
                logger.debug("log in other thread");
            }
        })).start();

        // 异步线程池打印日志,用MDCRunnable装饰Runnable
        EXECUTOR.execute(new MDCRunnable(new Runnable() {
            @Override
            public void run() {
                logger.debug("log in other thread pool");
            }
        }));
        EXECUTOR.shutdown();

参考文章

https://mp.weixin.qq.com/s/VwKmDCgeIvtnRfGUh-_pfQ
https://blog.youkuaiyun.com/qq_17522211?spm=1000.2115.3001.5343

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小安灬

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值