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

Spring Boot 3 Micrometer Trace/Span ID无法在Controller外传播问题

Spring Boot 3 Micrometer追踪:非Controller组件Trace/Span ID缺失问题解决

问题场景

从Spring Boot 2.x + Spring Cloud Sleuth迁移到Spring Boot 3.x + Micrometer Tracing后,仅@RestController内的日志能正常输出Trace ID和Span ID,但Filter收尾日志、SecurityAuditService等非Controller组件的日志里完全看不到这两个ID,调试发现同线程下ThreadLocal中的TraceContext是空的。

核心原因

  1. 上下文绑定时机差异:Sleuth会在请求刚进入时就把TraceContext绑定到ThreadLocal,而Micrometer默认的ObservationWebFilter绑定上下文的时机偏晚,部分自定义Filter、Spring Security组件可能在它之前执行,或者在上下文解绑后才输出日志。
  2. MDC生命周期不匹配:Micrometer依赖Observation管理TraceContext,请求处理完成后Observation结束,MDC里的traceId/spanId会被自动清除,如果组件的日志输出在这之后,自然拿不到ID。
  3. 第三方组件未接入Observation:RabbitMQ日志、OAuth2资源服务这类独立组件没有主动关联Micrometer的Observation上下文,没法继承请求的追踪信息。

解决方案

1. 调整ObservationWebFilter的执行优先级

让它在大部分自定义Filter、Security Filter之前执行,确保请求进入时尽早绑定上下文:

spring:
  servlet:
    filter:
      observation:
        order: -100 # 优先级设为较高值,确保早执行
  mvc:
    observation:
      enabled: true # 开启MVC观测支持

2. 手动绑定Observation上下文(针对自定义组件)

对于SecurityAuditService这类无法自动接入的组件,手动把当前Observation上下文绑定到线程:

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Component
public class SecurityAuditService {
    private static final Logger log = LoggerFactory.getLogger(SecurityAuditService.class);
    private final ObservationRegistry observationRegistry;

    public SecurityAuditService(ObservationRegistry observationRegistry) {
        this.observationRegistry = observationRegistry;
    }

    public void auditUserAction(String action) {
        // 绑定当前存在的Observation上下文
        try (Observation.Scope scope = Observation.getCurrentObservation(observationRegistry).openScope()) {
            log.info("用户执行操作: {}", action);
        }
    }
}

3. 避免MDC被过早清除

如果有自定义Filter在请求结束时手动清除了MDC,或者Observation结束时机太早,可自定义ObservationHandler调整MDC的清除时机:

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationHandler;
import org.springframework.stereotype.Component;

@Component
public class MdcRetentionHandler implements ObservationHandler<Observation.Context> {
    @Override
    public boolean supportsContext(Observation.Context context) {
        return true;
    }

    @Override
    public void onStart(Observation.Context context) {
        // 保留默认的MDC注入逻辑
    }

    @Override
    public void onStop(Observation.Context context) {
        // 不在这里清除MDC,改为通过Servlet RequestListener在请求完全结束时清理
        // 或者根据业务需求延迟清除
    }
}

4. 异步组件(如RabbitMQ)的上下文传递

对于RabbitMQ这类异步场景,需要手动把Trace ID传递到消息两端:

生产者端:把Trace ID写入消息头

import io.micrometer.tracing.Tracer;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessagePostProcessor;
import org.springframework.stereotype.Component;

@Component
public class TraceIdMessageProcessor implements MessagePostProcessor {
    private final Tracer tracer;

    public TraceIdMessageProcessor(Tracer tracer) {
        this.tracer = tracer;
    }

    @Override
    public Message postProcessMessage(Message message) {
        if (tracer.currentSpan() != null) {
            message.getMessageProperties().setHeader("traceId", tracer.currentSpan().context().traceId());
            message.getMessageProperties().setHeader("spanId", tracer.currentSpan().context().spanId());
        }
        return message;
    }
}

消费者端:读取消息头并绑定上下文

import io.micrometer.observation.Observation;
import io.micrometer.observation.ObservationRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;

@Component
public class AuditMessageListener {
    private static final Logger log = LoggerFactory.getLogger(AuditMessageListener.class);
    private final ObservationRegistry observationRegistry;

    public AuditMessageListener(ObservationRegistry observationRegistry) {
        this.observationRegistry = observationRegistry;
    }

    @RabbitListener(queues = "audit-queue")
    public void handleAuditMessage(Message message) {
        String traceId = (String) message.getMessageProperties().getHeader("traceId");
        String spanId = (String) message.getMessageProperties().getHeader("spanId");

        // 创建Observation并绑定上下文
        Observation observation = Observation.createNotStarted("rabbitmq-audit-consumer", observationRegistry)
                .lowCardinalityKeyValue("traceId", traceId)
                .lowCardinalityKeyValue("spanId", spanId);
        
        try (Observation.Scope scope = observation.start().openScope()) {
            log.info("收到审计消息: {}", new String(message.getBody()));
        }
    }
}

验证要点

  1. 发起请求后,检查Filter收尾日志、SecurityAuditService的日志是否包含Trace/Span ID
  2. 调试时确认Tracer.currentSpan()在目标组件中不为空
  3. 验证RabbitMQ消息的生产者、消费者日志是否携带追踪ID

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.25 07:08:14