relax-track-rest-spring-boot-starter

请求响应接口日志

引入该模块将会开启 Spring MVC 接口的日志功能,自动记录请求响应日志,并提供以下可配置参数:

track.record.rest.include-headers=true
track.record.rest.include-bodies=true
track.record.rest.exclude-patterns[0]=^/user.*
  • 日志中是否记录请求头
  • 日志中是否记录请求体
  • 需要忽略日志的接口正则,上例中 /user 开头的接口将不会记录日志,内置的正则有:
    • ^/swagger-ui.*
    • ^/v3/api-docs.*
    • ^/actuator.*

基于 MDC 的追踪信息

该模块将会尝试从接口请求头中解析两个用于追踪的参数,如果请求头中未提供,则会自动生成一组追踪参数并在该请求链路中使用:

  • X-Request-ID
  • X-Correlation-ID

在当前请求链路中,基于 slf4j 的 MDC 将会持有这组参数,用于日志打印中携带追踪信息,以 logback 配置为例,在 appender 的 pattern 中引用并打印追踪参数:

<appender name="console" class="ch.qos.logback.core.ConsoleAppender">
    <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
        <level>ALL</level>
    </filter>
    <layout class="ch.qos.logback.classic.PatternLayout">
        <pattern>[%X{X-Request-ID},%X{X-Correlation-ID}] %date{ISO8601} %-5level [%thread] %logger{32}:%L > %message%n
        </pattern>
    </layout>
</appender>

配置后的日志效果如下:

[4391c8432aa845dab1c0fe3e18fbc611,adbe088f32a54d009ef1115c95ac4c3b] 2024-05-09 11:01:10,633 INFO  [http-nio-8080-exec-1] ....

MDC Context 线程传递

1. 直接子线程

直接在当前请求中创建子线程,能够正确传递 MDC Context。

@RestController
public class DemoController {
    
    @GetMapping("/test")
    public void test() {
        new Thread() {
          @Override
          public void run() {
            log.info("This log can get the correct MDC Context.");
          }
        }.join();
    }
}

2. 手动提交线程池

对于任意一个线程池,在提交任务到线程池时手动设置好 MDC Context,完成传递:

import com.infilos.spring.track.TrackSupport;

ExecutorService PLAIN_EXECUTOR = Executors.newCachedThreadPool();

PLAIN_EXECUTOR.submit(TrackSupport.wrapMDCContext(() -> {
    log.info("This log can get the correct MDC Context.");
    return 0;
}));

3. 扩展线程池行为

手动提交线程池时需要每次都手动传递上下文,可以选择直接扩展线程池的行为,在线程池内实现传递逻辑,避免每次提交时操作:

import com.infilos.spring.track.concurrent.MDCAwareThreadPoolExecutor;

ThreadPoolExecutor MDC_EXECUTOR = new MDCAwareThreadPoolExecutor(
    4, 4, 1, TimeUnit.MINUTES,
    new SynchronousQueue<>(),
    Executors.defaultThreadFactory(),
    new ThreadPoolExecutor.AbortPolicy()
);
CompletableFuture.supplyAsync(() -> {
    log.info("This log can get the correct MDC Context.");
    return 0;
}, MDC_EXECUTOR);

4. Spring @Async

使用 Spring @Async 注解的异步方法,需要给 Spring 使用的线程池设置上传递 MDC Context 的装饰器:

import com.infilos.spring.track.TrackSupport;

@EnableAsync
@Configuration
public class AsyncExecutorConfigure extends AsyncConfigurerSupport {
    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new TaskExecutorBuilder()
                // omitted add other configs here...
                .taskDecorator(TrackSupport.getMDCContextTaskDecorator())
                .build();
        executor.initialize();
  
        return executor;
    }
}

5. CompletableFuture

CompletableFuture 的情况较复杂,目前未提供支持,更多信息可以参考这篇文章.

MDC Context 跨服务传递

1. Spring RestTemplate

提供了一个拦截器用于在发起请求时,将当前 MDC Context 中的信息设置到请求头中。

import com.infilos.spring.track.config.RestTemplateInterceptor;

@Configuration
public class RestTemplateConfigure {
  @Bean
  public RestTemplate restTemplate() {
    RestTemplate restTemplate = new RestTemplate();
    restTemplate.setInterceptors(Arrays.asList(new RestTemplateInterceptor()));
    return restTemplate;
  }
}

2. Unirest

使用 Unirest 发起请求时提供了对应的拦截器来传递 MDC Context,更多细节请查看这篇介绍