1.首先利用aop为所有请求入口添加requestid。
@Aspect
@Component
public class LoggingAspect {
/**
* AOP注解的Controller类方法必须为 public 或 protect ,千万不能用private!!!!!!!!否则会@Autowired注入的service会报空指针异常。
* 私有方法和字段不属于Spring上下文中的bean属性。
*/
@Around("@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PostMapping) || " +
"@annotation(org.springframework.web.bind.annotation.PutMapping) || " +
"@annotation(org.springframework.web.bind.annotation.DeleteMapping)")
public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 在logback-spring.xml里对应%X{requestId}
MDC.put("requestId", UUID.randomUUID().toString().substring(0, 13)); // Add request ID to MDC
try {
return joinPoint.proceed(); // Execute method
} finally {
MDC.remove("requestId"); // Remove request ID from MDC
}
}
}2.定义一下 logback-spring.xml ,引入 requestId 来进行链路记录,关键代码是 %X{requestId}
debug ${CONSOLE_LOG_PATTERN} UTF-8 ${LOG_FILE_PATH}/debug.log %d{yyyy-MM-dd HH:mm:ss.SSS} %X{requestId} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${LOG_FILE_PATH}/debug-%d{yyyy-MM-dd}.%i.log 100MB 15 1GB debug ACCEPT DENY ${LOG_FILE_PATH}/info.log %d{yyyy-MM-dd HH:mm:ss.SSS} %X{requestId} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${LOG_FILE_PATH}/info-%d{yyyy-MM-dd}.%i.log 100MB 15 info ACCEPT DENY ${LOG_FILE_PATH}/warn.log %d{yyyy-MM-dd HH:mm:ss.SSS} %X{requestId} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${LOG_FILE_PATH}/warn-%d{yyyy-MM-dd}.%i.log 100MB 15 warn ACCEPT DENY ${LOG_FILE_PATH}/error.log %d{yyyy-MM-dd HH:mm:ss.SSS} %X{requestId} [%thread] %-5level %logger{50} - %msg%n UTF-8 ${LOG_FILE_PATH}/error-%d{yyyy-MM-dd}.%i.log 100MB 15 ERROR ACCEPT DENY
2.定义一个`复杂`业务流程,来看看日志的实力
private final ExecutorService executorService = Executors.newFixedThreadPool(4);
@GetMapping("saveUser")
public String saveUser() {
log.info("进入了saveUser");
// 异步
CompletableFuture.runAsync(()-> b(), executorService);
log.info("退出了saveUser");
return "Ok";
}
private void b() {
log.info("进入了b");
}日志如下,可以看到异步线程的requestId丢失了,21dbaad3-3158 这个就是requestId,这种情况下我们需要自定义线程类来保存MDC的上下文
2023-04-19 11:51:59.309 21dbaad3-3158 INFO 23044 --- [p-nio-80-exec-1] c.h.m.api.CompletableFutureApi : 进入了saveUser2023-04-19 11:51:59.312 21dbaad3-3158 INFO 23044 --- [p-nio-80-exec-1] c.h.m.api.CompletableFutureApi : 退出了saveUser2023-04-19 11:51:59.312 INFO 23044 --- [pool-1-thread-1] c.h.m.api.CompletableFutureApi : 进入了b
3.定义线程实现类,并且在构造函数中存储MDC的上下文
立即学习“Java免费学习笔记(深入)”;
public static class MdcTaskWrapper implements Runnable {
private final Runnable task;
private final Map contextMap;
public MdcTaskWrapper(Runnable task) {
this.task = task;
this.contextMap = MDC.getCopyOfContextMap();
}
@Override
public void run() {
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
try {
task.run();
} finally {
MDC.clear();
}
}
} 4.接下来再改写一下runAsync的使用方式,我们用MdcTaskWrapper来进行线程操作,这个线程类是包含mdc上下文的
@GetMapping("saveUser")
public String saveUser() {
log.info("进入了saveUser");
// 异步
CompletableFuture.runAsync(this::b, command -> executorService.execute(new MdcTaskWrapper(command)));
log.info("退出了saveUser");
return "Ok";
}
private void b() {
log.info("进入了b");
}可以看到,requestId:4ab037ab-92cb,异步线程能够拿到MDC的上下文,并且成功记录链路日志
2023-04-19 11:58:27.581 4ab037ab-92cb INFO 6816 --- [p-nio-80-exec-5] c.h.m.api.CompletableFutureApi : 进入了saveUser
2023-04-19 11:58:27.582 4ab037ab-92cb INFO 6816 --- [p-nio-80-exec-5] c.h.m.api.CompletableFutureApi : 退出了saveUser
2023-04-19 11:58:27.582 4ab037ab-92cb INFO 6816 --- [pool-1-thread-1] c.h.m.api.CompletableFutureApi











