You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot 3中Micrometer Tracing与MDC集成问题咨询

Spring Boot 3 + Micrometer 自定义链路追踪与MDC管理问题

需求背景

基于Logback(Slf4j)实现自定义链路追踪与日志管理,需从请求头读取字段加入关联信息,同时向MDC添加自定义字段用于日志输出。从Spring Boot 2的Sleuth切换到Spring Boot 3的Micrometer后,遇到MDC数据管理、自定义字段添加、线程上下文传递的问题,具体疑问:

  1. 如何正确向关联信息添加自定义字段?
  2. 使用Micrometer时如何填充MDC?
  3. 如何将上下文从父线程传递到新线程,并在任务完成后清理?

已完成的配置

1. 日志格式配置

logging:
  pattern:
    console: "%clr(%d){faint} %clr(%5p) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%X{testRemoteBaggage}-%X{testLocalBaggage}]){magenta} %clr([%40.40t]){faint} %clr(${LOG_CORRELATION_PATTERN:-}){faint}%clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n%wEx"

2. 自定义关联字段配置

management:
  tracing:
    sampling:
      probability: 1.0
    baggage:
      correlation:
        fields: testRemoteBaggage, testLocalBaggage
      remote-fields: testRemoteBaggage
      local-fields: testLocalBaggage

3. 虚拟线程异步任务执行器配置

application.yml

spring:
  threads:
    virtual:
      enabled: true

配置类

@EnableAsync
@Configuration(proxyBeanMethods = false)
public class DummyJsonTracingConfiguration {

    @Bean
    public AsyncTaskExecutor applicationTaskExecutor() {
        return new TaskExecutorAdapter(Executors.newVirtualThreadPerTaskExecutor());
    }

    @Bean
    public TomcatProtocolHandlerCustomizer<?> protocolHandlerVirtualThreadExecutorCustomizer() {
        return protocolHandler -> {
            protocolHandler.setExecutor(Executors.newVirtualThreadPerTaskExecutor());
        };
    }
}

4. 自定义Filter添加关联字段

@Component
@Slf4j
@RequiredArgsConstructor
public class AddBaggageFilter extends OncePerRequestFilter implements OrderedFilter {
    private static final String fieldRemote = "testRemoteBaggage";
    private static final String fieldLocal = "testLocalBaggage";

    private final Tracer tracer;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

        String baggagefieldRemote = request.getHeader(fieldRemote);
        if (StringUtils.isEmpty(baggagefieldRemote)) {
            baggagefieldRemote = "autoValue"+fieldRemote;
        }
        String baggagefieldLocal = request.getHeader(fieldLocal);
        if (StringUtils.isEmpty(baggagefieldLocal)) {
            baggagefieldLocal = "autoValue"+fieldLocal;
        }
        log.warn("baggagefieldRemote value : [{}]", baggagefieldRemote);
        log.warn("baggagefieldLocal value : [{}]", baggagefieldLocal);

        try (BaggageInScope baggageRemote = tracer.createBaggageInScope(fieldRemote, baggagefieldRemote);
             BaggageInScope baggageLocal = tracer.createBaggageInScope(fieldLocal, baggagefieldLocal)) {
            log.warn("baggagefieldRemote was created!");
            filterChain.doFilter(request, response);
        }
    }

    @Override
    public int getOrder() {
        return Ordered.HIGHEST_PRECEDENCE + 2;
    }
}

当前问题

日志中仅主线程显示关联信息,异步线程(如ForkJoinPool中的线程)的日志丢失了testRemoteBaggage和testLocalBaggage字段:

2024-07-14 14:05:28,274  WARN 23716 --- [test1-] [                        tomcat-handler-1] [6693bf08c4ecb0e29507c9dbc43570fe-9507c9dbc43570fe] b.g.t.rest.AddBaggageFilter              : baggagefieldRemote value : [test1]
2024-07-14 14:05:28,275  WARN 23716 --- [test1-] [                        tomcat-handler-1] [6693bf08c4ecb0e29507c9dbc43570fe-9507c9dbc43570fe] b.g.t.rest.AddBaggageFilter              : baggagefieldLocal value : [autoValuetestLocalBaggage]
2024-07-14 14:05:28,276  WARN 23716 --- [test1-autoValuetestLocalBaggage] [                        tomcat-handler-1] [6693bf08c4ecb0e29507c9dbc43570fe-9507c9dbc43570fe] b.g.t.rest.AddBaggageFilter              : baggagefieldRemote was created!
2024-07-14 14:05:28,279 DEBUG 23716 --- [test1-autoValuetestLocalBaggage] [                        tomcat-handler-1] [6693bf08c4ecb0e29507c9dbc43570fe-9507c9dbc43570fe] o.s.web.servlet.DispatcherServlet        : POST "/categoryInfo", parameters={}
2024-07-14 14:05:28,285 DEBUG 23716 --- [test1-autoValuetestLocalBaggage] [                        tomcat-handler-1] [6693bf08c4ecb0e29507c9dbc43570fe-9507c9dbc43570fe] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to by.gvu.testtracingrestclient.controller.SimpleController#getListProducts()
2024-07-14 14:05:29,128 DEBUG 23716 --- [test1-autoValuetestLocalBaggage] [                        tomcat-handler-1] [6693bf08c4ecb0e29507c9dbc43570fe-9507c9dbc43570fe] o.s.web.client.DefaultRestClient         : Reading to [java.util.List&lt;java.lang.String&gt;]
2024-07-14 14:05:29,377 DEBUG 23716 --- [-] [        ForkJoinPool.commonPool-worker-9] [                                                 ] o.s.web.client.DefaultRestClient         : Reading to [java.util.List&lt;by.gvu.testtracingrestclient.model.Product&gt;]
2024-07-14 14:05:29,689 DEBUG 23716 --- [-] [       ForkJoinPool.commonPool-worker-10] [                                                 ] o.s.web.client.DefaultRestClient         : Reading to [java.util.List&lt;by.gvu.testtracingrestclient.model.Product&gt;]
2024-07-14 14:05:29,734 DEBUG 23716 --- [-] [        ForkJoinPool.commonPool-worker-4] [                                                 ] o.s.web.client.DefaultRestClient         : Reading to [java.util.List&lt;by.gvu.testtracingrestclient.model.Product&gt;]
2024-07-14 14:05:29,734 DEBUG 23716 --- [-] [       ForkJoinPool.commonPool-worker-12] [                                                 ] o.s.web.client.DefaultRestClient         : Reading to [java.util.List&lt;by.gvu.testtracingrestclient.model.Product&gt;]

更新:问题与虚拟线程无关。


解决方案

1. 正确向关联信息添加自定义字段

你当前的配置已经正确定义了自定义baggage字段,核心规则如下:

  • remote-fields:配置的字段会在跨服务调用时通过请求头传递,需确保上下游服务配置相同字段
  • local-fields:仅当前服务内部使用,不会传递到其他服务
  • 代码中通过tracer.createBaggageInScope设置字段值的方式是正确的,Filter设置Ordered.HIGHEST_PRECEDENCE + 2的顺序也没问题,能保证在业务逻辑执行前完成baggage初始化。

额外优化:Micrometer支持自动从请求头提取同名baggage字段,若无需自定义默认值逻辑,可以去掉Filter中的头读取代码,依赖框架自动注入。

2. 使用Micrometer时如何填充MDC

Micrometer Tracing会自动将management.tracing.baggage.correlation.fields中的字段同步到MDC,无需手动添加。你的配置已经将testRemoteBaggage和testLocalBaggage加入该列表,只要baggage存在,MDC就会自动填充对应字段。

可通过业务代码中调用MDC.get("testRemoteBaggage")验证同步效果。

3. 线程上下文传递与清理

异步线程丢失上下文的核心原因是:默认线程池(包括ForkJoinPool)未配置上下文传递包装器,Micrometer上下文不会自动传递到非Spring管理的异步任务中。

解决步骤:

(1)为自定义线程池添加上下文包装

修改applicationTaskExecutor的配置,用ContextExecutorService包装线程池,确保上下文传递:

@Bean
public AsyncTaskExecutor applicationTaskExecutor() {
    ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
    // 包装线程池,传递Micrometer上下文
    ExecutorService wrappedExecutor = ContextExecutorService.wrap(executor, ContextSnapshot::captureAll);
    return new TaskExecutorAdapter(wrappedExecutor);
}

(2)处理ForkJoinPool的上下文传递

若代码中使用CompletableFuture等默认依赖ForkJoinPool的异步操作,需手动包装任务,或配置全局自定义ForkJoinPool:

  • 提交任务时直接包装:
CompletableFuture.runAsync(ContextSnapshot.wrap(() -> {
    // 异步业务逻辑
}));
  • 配置全局自定义ForkJoinPool:
@Bean
public ForkJoinPool forkJoinPool() {
    return new ForkJoinPool(
            Runtime.getRuntime().availableProcessors(),
            ForkJoinPool.defaultForkJoinWorkerThreadFactory,
            null,
            false
    );
}

// 使用时注入并包装任务
@Autowired
private ForkJoinPool forkJoinPool;

CompletableFuture.runAsync(ContextSnapshot.wrap(() -> {
    // 业务逻辑
}), forkJoinPool);

(3)上下文清理

你在Filter中使用的try-with-resources方式会自动清理BaggageInScope上下文,是正确的。对于异步任务,ContextSnapshot.wrap会在任务执行完成后自动恢复原上下文,无需手动清理。

额外注意点

  • 确保引入正确依赖:micrometer-tracing-bridge-brave(Spring Boot 3 starter已默认包含,需检查版本匹配)
  • 使用@Async注解时,Spring会自动使用你配置的applicationTaskExecutor,只要线程池已被上下文包装,异步方法会自动继承父线程上下文。

内容的提问来源于stack exchange,提问作者Heorhi Utseuski

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 23:48:09