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是空的。
核心原因
- 上下文绑定时机差异:Sleuth会在请求刚进入时就把TraceContext绑定到ThreadLocal,而Micrometer默认的
ObservationWebFilter绑定上下文的时机偏晚,部分自定义Filter、Spring Security组件可能在它之前执行,或者在上下文解绑后才输出日志。 - MDC生命周期不匹配:Micrometer依赖Observation管理TraceContext,请求处理完成后Observation结束,MDC里的traceId/spanId会被自动清除,如果组件的日志输出在这之后,自然拿不到ID。
- 第三方组件未接入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())); } } }
验证要点
- 发起请求后,检查Filter收尾日志、SecurityAuditService的日志是否包含Trace/Span ID
- 调试时确认
Tracer.currentSpan()在目标组件中不为空 - 验证RabbitMQ消息的生产者、消费者日志是否携带追踪ID
内容的提问来源于stack exchange,提问作者SeverityOne
相关产品推荐
相关产品推荐

