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

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.csdn.net/qq_17522211?spm=1000.2115.3001.5343

猜你喜欢

转载自blog.csdn.net/qq_17522211/article/details/128535882