将新Micrometer追踪上下文传递至Reactor Mono问题求助
问题分析
核心问题是线程本地的Trace Scope与Reactor反应式上下文不共享:
- 通过
tracer.currentTraceContext().newScope(tc)创建的线程本地Scope仅对当前线程的同步代码生效(即try块内的第一个LOGGER.info)。 - Mono属于异步执行逻辑,代码会在订阅时的线程或其他线程运行,不会自动继承当前线程的Scope,因此内部日志和方法调用仍沿用原traceId1。
- 你手动调用
contextWrite(Context.of(TraceContext.class, tc))注入的上下文,未与追踪框架的上下文绑定,导致日志框架无法识别。
解决方案
1. 正确将TraceContext注入Reactor Context
以OpenTelemetry为例,需使用官方提供的Reactor工具类传递TraceContext(若用Spring Boot,通常已集成相关依赖):
依赖引入(Maven示例)
<dependency> <groupId>io.opentelemetry</groupId> <artifactId>opentelemetry-extension-reactor</artifactId> </dependency>
代码调整
TraceContext tc = tracer.traceContextBuilder() .traceId(message.getTraceId()) // traceId0 .spanId(tracer.currentTraceContext().context().spanId()) .sampled(tracer.currentTraceContext().context().sampled()) .parentId(tracer.currentTraceContext().context().parentId()) .build(); // 用OpenTelemetry上下文传播工具包装Mono,确保TraceContext在反应式流中传递 return OpenTelemetry.contextPropagation() .wrap(Mono.deferContextual(contextView -> { LOGGER.info("tracing log -> test"); // 现在将使用traceId0 return switch (message.getEvent()) { case ADD_EVENT -> add(message.get(), message.getTraceId()); case REMOVE_EVENT -> remove(message.get(), message.getTraceId()); default -> { LOGGER.warn("not supported '{}' ", message); yield Mono.just(true); } }; })) .contextWrite(Context.of(ContextKey.named("otel.trace.context"), tc)) .contextCapture();
2. 配置日志框架从Reactor Context读取Trace信息
若使用SLF4J+Logback,需确保MDC能从Reactor Context中读取Trace参数:
- 启动时注册Reactor MDC钩子:
ReactorHooks.onEachOperator(MdcContextWrite.create());
- 或手动在Mono中注入MDC上下文:
return OpenTelemetry.contextPropagation() .wrap(Mono.deferContextual(...)) .contextWrite(MdcContextWrite.of(mdc -> { mdc.put("traceId", tc.traceId()); mdc.put("spanId", tc.spanId()); return mdc; })) .contextWrite(Context.of(ContextKey.named("otel.trace.context"), tc)) .contextCapture();
3. 移除多余的线程本地Scope
原try (var sc = tracer.currentTraceContext().newScope(tc))仅对同步代码生效,对异步Mono无帮助,反而可能造成上下文混乱,建议直接移除。
简化后完整代码示例
import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.ContextKey; import io.opentelemetry.extension.reactor.OpenTelemetry; import reactor.core.publisher.Mono; // ... public Mono<Boolean> handleMessage(Message message, Tracer tracer) { TraceContext tc = tracer.traceContextBuilder() .traceId(message.getTraceId()) // traceId0 .spanId(tracer.currentTraceContext().context().spanId()) .sampled(tracer.currentTraceContext().context().sampled()) .parentId(tracer.currentTraceContext().context().parentId()) .build(); return OpenTelemetry.contextPropagation() .wrap(Mono.deferContextual(contextView -> { LOGGER.info("tracing log -> test"); // 使用traceId0 return switch (message.getEvent()) { case ADD_EVENT -> add(message.get(), message.getTraceId()); case REMOVE_EVENT -> remove(message.get(), message.getTraceId()); default -> { LOGGER.warn("not supported '{}' ", message); yield Mono.just(true); } }; })) .contextWrite(Context.of(ContextKey.named("otel.trace.context"), tc)) .contextCapture(); }
关键说明
- 反应式流上下文与线程本地变量相互隔离,必须通过Reactor的
contextWrite+框架上下文传播工具传递Trace信息。 - 日志框架需配置为从Reactor Context读取MDC参数,否则日志仍会显示旧traceId。
- 若使用Spring Cloud Sleuth,无需手动构建TraceContext,直接用
SleuthTracer的withTraceId方法包装Mono即可,实现更简洁。
内容的提问来源于stack exchange,提问作者nmanandhan
相关产品推荐
相关产品推荐

