springboot整合MDC实现链路日志的应用

链路日志

在日常项目中我们大量使用日志来进行记录服务运行情况, 但是当数据量变大, 接口频次变高,就会出现线程号重复的情况,在排查问题是不能及时定位到相关点上。基于此场景下,MDC就应运而生了, 我也是最近才在项目中引入的;之前项目一直没有使用,引入后,查询日志什么的也都方便很多。

新项目使用

涉及以下几个方面进行改造
1.正常http请求产生的线程号
2.线程池产生的线程号
3.异步产生的线程号
4.消费者相关产生的线程号

直接上代码(会解释相关部分):
拦截器部分

public class LogInterceptor implements HandlerInterceptor {
    
    private static final String TRACE_ID = "TRACE_ID";

    /**
     * 前置拦截器
     * @author freedom
     * @date 2022/12/28 17:43
     * @param request 请求
     * @param response 响应
     * @param handler 处理器
     * @return {@link boolean}
     */
    @Override
    public boolean preHandle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception {
        String tid = RandomStringUtils.randomAlphanumeric(8);
        if(!StringUtils.isEmpty(request.getHeader(TRACE_ID))){
            tid=request.getHeader(TRACE_ID);
        }
        //利用MDC将请求存储
        MDC.put(TRACE_ID,tid);
        return true;
    }

    /**
     * 后置处理器
     * @author freedom
     * @date 2022/12/28 17:43
     * @param request 请求
     * @param response 响应
     * @param handler 处理器
     * @param ex  异常
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        MDC.remove(TRACE_ID);
    }
}

注册拦截器

@Configuration
public class WebConfigurerAdapter implements WebMvcConfigurer {

    @Bean
    public LogInterceptor logInterceptor() {
        return new LogInterceptor();
    }

    /**
     * 注册拦截器
     * @author freedom
     * @date 2022/12/28 17:46
     * @param registry  registry
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(logInterceptor());
    }
}

MDC工具类(用于线程池相关)

public final class ThreadMdcUtil {
    private static final String TRACE_ID = "TRACE_ID";

    // 获取唯一性标识
    public static String generateTraceId() {
        return RandomStringUtils.randomAlphanumeric(8);
    }

    public static void setTraceIdIfAbsent() {
        if (MDC.get(TRACE_ID) == null) {
            MDC.put(TRACE_ID, generateTraceId());
        }
    }

    /**
     * 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程
     * @author freedom
     * @date 2022/12/28 17:52
     * @param callable callable
     * @param context  context
     * @return {@link Callable<T>}
     */
    public static <T> Callable<T> wrap(final Callable<T> callable,final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                return callable.call();
            } finally {
                MDC.clear();
            }
        };
    }


    /**
     * 用于父线程向线程池中提交任务时,将自身MDC中的数据复制给子线程
     * @author freedom
     * @date 2022/12/28 17:52
     * @param runnable runnable
     * @param context  context
     * @return {@link Runnable}
     */
    public static Runnable wrap(final Runnable runnable, final Map<String, String> context) {
        return () -> {
            if (context == null) {
                MDC.clear();
            } else {
                MDC.setContextMap(context);
            }
            setTraceIdIfAbsent();
            try {
                runnable.run();
            } finally {
                MDC.clear();
            }
        };
    }
}

本地线程池配置管理

public class LocalThreadPoolTaskExecutor extends ThreadPoolTaskExecutor {

    public LocalThreadPoolTaskExecutor () {
        super();
    }

    @Override
    public void execute(Runnable task) {
        super.execute(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }


    @Override
    public <T> Future<T> submit(Callable<T> task) {
        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }

    @Override
    public Future<?> submit(Runnable task) {
        return super.submit(ThreadMdcUtil.wrap(task, MDC.getCopyOfContextMap()));
    }
}

异步使用的池

@Configuration
@EnableAsync
public class ThreadPoolConfig {
    /**
     * 异步方法线程池
     * @author freedom
     * @date 2022/12/28 17:36
     * @return {@link Executor}
     */
    @Bean("asyncExecutor")
    public Executor asyncExecutor() {
        LocalThreadPoolTaskExecutor executor = new LocalThreadPoolTaskExecutor();
        executor.setCorePoolSize(6);
        executor.setMaxPoolSize(12);
        executor.setQueueCapacity(500);
        executor.setKeepAliveSeconds(0);
        executor.setThreadNamePrefix("asyncExecutor");
        executor.initialize();
        return executor;
    }
}

本地自定义线程工厂

public class ThreadPoolExecutorFactory{

    public static Executor get() {
        LocalThreadPoolTaskExecutor executor = new LocalThreadPoolTaskExecutor();
        executor.setCorePoolSize(6);
        executor.setMaxPoolSize(12);
        executor.setQueueCapacity(500);
        executor.setKeepAliveSeconds(0);
        executor.initialize();
        return executor;
    }

    public static Executor get(Integer queueSize) {
        LocalThreadPoolTaskExecutor executor = new LocalThreadPoolTaskExecutor();
        executor.setCorePoolSize(6);
        executor.setMaxPoolSize(12);
        executor.setQueueCapacity(queueSize);
        executor.setKeepAliveSeconds(0);
        executor.initialize();
        return executor;
    }


    public static Executor getCallerRunsPolicy(Integer queueSize){

        LocalThreadPoolTaskExecutor executor = new LocalThreadPoolTaskExecutor();
        executor.setCorePoolSize(6);
        executor.setMaxPoolSize(12);
        executor.setQueueCapacity(queueSize);
        executor.setKeepAliveSeconds(0);
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        executor.initialize();
        return executor;
    }


}

日志测试异步方法

    @SneakyThrows
    @Async(value="asyncExecutor")
    public void getAsyncLog () {
        Thread.sleep(3000);
        log.info("异步方法日志打印....");
    }

http请求

@RestController
@Slf4j
@RequestMapping("/log")
public class LogDemoController {
    @Resource
    private CrmDataService crmDataService ;
    // 线程池使用中的日志打印
    public static final Executor threadPoolExecutor = ThreadPoolExecutorFactory.get(100);
    @GetMapping(value = "/getLog")
    public String getLog() {
        log.info("链路日志测试开始!");
        crmDataService.getAsyncLog();
        threadPoolExecutor.execute(()-> log.info("链路日志改造test"));
        return "hello world!";
    }
}

日志配置 [TRACE_ID]

  <property name="LOG_PATTERN"
              value="[%d{yyyy-MM-dd HH:mm:ss.SSS,GMT+8}] [%-5level] [%class{0}:%method:%line] -[%X{TRACE_ID}]
 [%t] [-[%msg]-] %n"/>

kafka消费者这种

//获取生成的traceId
String traceId = ThreadMdcUtil.generateTraceId();
//使用MDC存入
MDC.put("TRACE_ID",traceId);
//使用完, 记住要remove掉, 建议放到finally里
MDC.remove("TRACE_ID");

针对于老项目改造

可将生成的工具类提取到公共模块, 其他模块进行引用使用就可以了(日志配置也需要修改)

我也是初次使用,有什么不足还请指出,大家共同探讨!

posted @ 2023-03-02 16:02  freedomlog  阅读(309)  评论(0编辑  收藏  举报