详解Java如何在CompletableFuture中实现日志记录

这篇文章主要为大家详细介绍了一种slf4j自带的MDC类,来记录完整的请求日志,和在CompletableFuture异步线程中如何保留链路id,需要的可以参考一下

平时写项目的时候,java之父叫我们多打日志,我们通常使用traceId和requestId来保存完整请求的链路日志,例如市面上的skywalking、zipkin等都能实现此功能。

下面介绍一种slf4j自带的MDC类,来记录完整的请求日志,和在CompletableFuture异步线程中如何保留链路id

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%nUTF-8 ${LOG_FILE_PATH}/debug-%d{yyyy-MM-dd}.%i.log 100MB151GB debugACCEPTDENY ${LOG_FILE_PATH}/info.log %d{yyyy-MM-dd HH:mm:ss.SSS} %X{requestId} [%thread] %-5level %logger{50} - %msg%nUTF-8 ${LOG_FILE_PATH}/info-%d{yyyy-MM-dd}.%i.log 100MB15 infoACCEPTDENY ${LOG_FILE_PATH}/warn.log %d{yyyy-MM-dd HH:mm:ss.SSS} %X{requestId} [%thread] %-5level %logger{50} - %msg%nUTF-8 ${LOG_FILE_PATH}/warn-%d{yyyy-MM-dd}.%i.log 100MB15 warnACCEPTDENY ${LOG_FILE_PATH}/error.log %d{yyyy-MM-dd HH:mm:ss.SSS} %X{requestId} [%thread] %-5level %logger{50} - %msg%nUTF-8 ${LOG_FILE_PATH}/error-%d{yyyy-MM-dd}.%i.log 100MB15 ERRORACCEPTDENY     

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           : 进入了saveUser
2023-04-19 11:51:59.312 21dbaad3-3158  INFO 23044 --- [p-nio-80-exec-1] c.h.m.api.CompletableFutureApi           : 退出了saveUser
2023-04-19 11:51:59.312   INFO 23044 --- [pool-1-thread-1] c.h.m.api.CompletableFutureApi           : 进入了b

3.定义线程实现类,并且在构造函数中存储MDC的上下文

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    

以上就是详解Java如何在CompletableFuture中实现日志记录的详细内容,更多关于Java CompletableFuture日志记录的资料请关注0133技术站其它相关文章!

以上就是详解Java如何在CompletableFuture中实现日志记录的详细内容,更多请关注0133技术站其它相关文章!

赞(0) 打赏
未经允许不得转载:0133技术站首页 » Java