SpringBoot 项目添加 MDC 日志链路追踪的执行流程

网友投稿 1298 2023-01-07

SpringBoot 项目添加 MDC 日志链路追踪的执行流程

SpringBoot 项目添加 MDC 日志链路追踪的执行流程

目录1. 线程池配置2. -配置3. 日志文件配置4. 使用方法示例4.1. 异步使用4.2. 定时任务

日志链路追踪的意思就是将一个标志跨线程进行传递,在一般的小项目中也就是在你新起一个线程的时候,或者使用线程池执行任务的时候会用到,比如追踪一个用户请求的完整执行流程。

这里用到MDC和ThreadLocal,分别由下面的包提供:

java.lang.ThreadLocal

org.slf4j.MDC

直接上代码

1. 线程池配置

如果你直接通过手动新建线程来执行异步任务,想要实现标志传递的话,需要自己去实现,其实和线程池一样,也是调用MDC的相关方法,如下所示:

//取出父线程的MDC

Map context = MDC.getCopyOfContextMap();

//将父线程的MDC内容传给子线程

MDC.setContextMap(context);

首先提供一个常量:

package com.example.demo.common.constant;

/**

* 常量

*

* @author wangbo

* @date 2021/5/13

*/

public class Constants {

public static final String LOG_MDC_ID = "trace_id";

}

接下来需要对ThreadPoolTaskExecutor的方法进行重写:

package com.example.demo.common.threadpool;

import com.example.demo.common.constant.Constants;

import lombok.extern.slf4j.Slf4j;

import org.slf4j.MDC;

import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.Map;

import java.util.UUID;

import java.util.concurrent.Callable;

import java.util.concurrent.Future;

/**

* MDC线程池

* 实现内容传递

*

* @author wangbo

* @date 2021/5/13

*/

@Slf4j

public class MdcTaskExecutor extends ThreadPoolTaskExecutor {

@Override

public Future submit(Callable task) {

log.info("mdc thread pool task executor submit");

Map context = MDC.getCopyOfContextMap();

return super.submit(() -> {

T result;

if (context != null) {

//将父线程的MDC内容传给子线程

MDC.setContextMap(context);

} else {

//直接给子线程设置MDC

MDC.put(Constants.LOG_MDC_ID, UUID.randomUUID().toString().replace("-", ""));

}

try {

//执行任务

result = task.call();

} finally {

try {

MDC.clear();

} catch (Exception e) {

log.warn("MDC clear exception", e);

}

}

return result;

});

}

@Override

public void execute(Runnable task) {

log.info("mdc thread pool task executor execute");

Map context = MDC.getCopyOfContextMap();

super.execute(() -> {

if (context != null) {

//将父线程的MDC内容传给子线程

MDC.setContextMap(context);

} else {

//直接给子线程设置MDC

MDC.put(Constants.LOG_MDC_ID, UUID.randomUUID().toString().replace("-", ""));

}

try {

//执行任务

task.run();

} finally {

try {

MDC.clear();

} catch (Exception e) {

log.warn("MDC clear exception", e);

}

}

});

}

}

然后使用自定义的重写子类MdcTaskExecutor来实现线程池配置:

package com.example.demo.common.threadpool;

import lombok.extern.slf4j.Slf4j;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.Configuration;

import java.util.concurrent.Executor;

import java.util.concurrent.ThreadPoolExecutor;

/**

* 线程池配置

*

* @author wangbo

* @date 20cuONjrI21/5/13

*/

@Slf4j

@Configuration

public class ThreadPoolConfig {

/**

* 异步任务线程池

* 用于执行普通的异步请求,带有请求链路的MDC标志

*/

@Bean

public Executor commonThreadPool() {

log.info("start init common thread pool");

//ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();

MdcTaskExecutor executor = new MdcTaskExecutor();

//配置核心线程数

executor.setCorePoolSize(10);

//配置最大线程数

executor.setMaxPoolSize(20);

//配置队列大小

executor.setQueueCapacity(3000);

//配置空闲线程存活时间

executor.setKeepAliveSeconds(120);

//配置线程池中的线程的名称前缀

executor.setThreadNamePrefix("common-thread-pool-");

//当达到最大线程池的时候丢弃最老的任务

executor.setRejectedExhttp://ecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());

//执行初始化

executor.initialize();

return executor;

}

/**

* 定时任务线程池

* 用于执行自启动的任务执行,父线程不带有MDC标志,不需要传递,直接设置新的MDC

* 和上面的线程池没啥区别,只是名字不同

*/

@Bean

public Executor scheduleThreadPool() {

log.info("start init schedule thread pool");

MdcTaskExecutor executor = new MdcTaskExecutor();

executor.setCorePoolSize(10);

executor.setMaxPoolSize(20);

executor.setQueueCapacity(3000);

executor.setKeepAliveSeconds(120);

executor.setThreadNamePrefix("schedule-thread-pool-");

executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());

executor.initialize();

return executor;

}

}

2. -配置

package com.example.demo.common.interceptor;

import com.example.demo.common.constant.Constants;

import lombok.extern.slf4j.Slf4j;

import org.slf4j.MDC;

import org.springframework.stereotype.Component;

import org.springframework.web.servlet.HandlerInterceptor;

import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;

import javax.servlet.http.HttpServletResponse;

import java.util.UUID;

/**

* 日志-

*

* @author wangbo

* @date 2021/5/13

*/

@Slf4j

@Component

public class LogInterceptor implements HandlerInterceptor {

@Override

public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

//log.info("进入 LogInterceptor");

//添加MDC值

MDC.put(Constants.LOG_MDC_ID, UUID.randomUUID().toString().replace("-", ""));

//打印接口请求信息

String method = request.getMethod();

String uri = request.getRequestURI();

log.info("[请求接口] : {} : {}", method, uri);

//打印请求参数

return true;

}

@Override

public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

//log.info("执行 LogInterceptor");

}

@Override

public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {

//log.info("退出 LogInterceptor");

//打印请求结果

//删除MDC值

MDC.remove(Constants.LOG_MDC_ID);

}

}

对-进行注册:

package com.example.demo.common.config;

import com.example.demo.common.interceptor.LogInterceptor;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Configuration;

import org.springframework.web.servlet.config.annotation.InterceptorRegistry;

import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**

* MVC配置

*

* @author wangbo

* @date 2021/5/13

*/

@Configuration

public class WebMvcConfig implements WebMvcConfigurer {

@Autowired

private LogInterceptor logInterceptor;

cuONjrI

/**

* -注册

*/

@Override

public void addInterceptors(InterceptorRegistry registry) {

registry.addInterceptor(logInterceptor);

}

}

3. 日志文件配置

需要在logback-spring.xml文件中的日志打印格式里添加%X{trace_id},如下所示:

%d{yyyy-MM-dd HH:mm:ss.SSS} [%X{trace_id}] [%level] [%thread] [%class:%line] - %m%n

UTF-8

4. 使用方法示例

4.1. 异步使用

这里注意,异步方法的调用不能直接调用当前类的方法,也就是说调用方法和异步方法不能在同一个类里,否则会变为同步执行。

/**

* 异步方法

*/

//@Async//这种写法,当只有一个线程池时,会使用该线程池执行,有多个则会使用SimpleAsyncTaskExecutor

@Async(value = "commonThreadPool")//指定执行的线程池

@Override

public void async() {

log.info("测试异步线程池");

}

4.2. 定时任务

package com.example.demo.generator.crontab;

import lombok.extern.slf4j.Slf4j;

import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

/**

* 定时任务

*

* @author wangbo

* @date 2021/5/14

*/

@Slf4j

@Component

public class TestTimeTask {

//基于注解@Scheduled默认为单线程,开启多个任务时,任务的执行时机会受上一个任务执行时间的影响。

//使用的线程池是taskScheduler,线程ID为scheduling-x

//添加@Async注解指定线程池,则可以多线程执行定时任务(原本是单线程的)。

/**

* 两次任务开始的时间间隔为2S

* 不使用线程池,单线程间隔则为4S。单线程保证不了这个2S间隔,因为任务执行耗时超过了定时间隔,就会影响下一次任务的执行

* 使用线程池,多线程执行,时间间隔为2S

*/

//@Async(value = "scheduleThreadPool")

//@Scheduled(fixedRate = 2000)

public void fixedRate() {

log.info("定时间隔任务 fixedRate = {}", LocalDateTime.now());

try {

Thread.sleep(4_000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

/**

* 下次任务的开始时间距离上次任务的结束时间间隔为2S

* 这种适合使用单线程,不适合使用线程池,单线程间隔则为6S。

* 用了线程池,和这个特性相背离了

*/

//@Scheduled(fixedDelay = 2_000)

public void fixedDelay() {

log.info("延迟定时间隔任务 fixedDelay = {}", LocalDateTime.now());

try {

Thread.sleep(4_000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

/**

* 首次延迟10S后执行fixedDelay类型间隔任务,也可以配置为fixedDelay类型间隔任务

* 控件第一次执行之前要延迟的毫秒数

* {@link # fixeddrate} or {@link #fixedDelay}

*/

//@Scheduled(initialDelay = 10_000, fixedDelay = 1_000)

public void initialDelay() {

log.info("首次延迟定时间隔任务 initialDelay = {}", LocalDateTime.now());

}

/**

* 这里使用线程池也是为了防止任务执行耗时超过了定时间隔,就会影响下一次任务的执行

*/

//@Async(value = "scheduleThreadPool")

//@Scheduled(cron = "0/2 * * * * *")

public void testCron() {

log.info("测试表达式定时任务 testCron = {}", LocalDateTime.now());

try {

Thread.sleep(4_000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:小程序生态文明论文怎么写(关于小程序的论文)
下一篇:金融小程序优缺点(微信小程序的优缺点)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~