WebClient链路追踪上下文丢失:如何让日志包含traceId与spanId?
SpringBoot 3.5.10中WebClient异步回调日志丢失traceId/spanId的解决方法
问题原因
WebClient的subscribe回调属于异步执行,默认情况下追踪上下文不会自动传递到回调线程,导致日志无法获取traceId和spanId。
解决步骤
1. 确认WebClient的观测配置正确性
确保WebClient.Builder已正确注入ObservationRegistry并启用观测支持,示例代码:
@Bean public WebClient webClient(ObservationRegistry observationRegistry) { return WebClient.builder() .observationRegistry(observationRegistry) .build(); }
如果使用自动配置的WebClient.Builder,可通过以下方式扩展:
@Bean public WebClient customWebClient(WebClient.Builder builder, ObservationRegistry observationRegistry) { return builder.observationRegistry(observationRegistry).build(); }
2. 手动传递追踪上下文到异步回调
方式一:使用Micrometer的ContextSnapshot
通过ContextSnapshotFactory捕获当前上下文,在回调中恢复:
@Autowired private ContextSnapshotFactory contextSnapshotFactory; // 业务方法中调用WebClient public void fetchDepartment() { ContextSnapshot snapshot = contextSnapshotFactory.captureAll(); webClient.get().uri("/api/department") .retrieve() .bodyToMono(Department.class) .subscribe(dept -> { try (ContextSnapshot.Scope scope = snapshot.setThreadContext()) { // 此处日志将包含traceId和spanId log.info("获取部门信息: {}", dept.getName()); } }); }
方式二:使用Brave的Tracer
直接捕获当前Span上下文,在回调中恢复:
@Autowired private Tracer tracer; // 业务方法中调用WebClient public void fetchDepartment() { Span currentSpan = tracer.currentSpan(); webClient.get().uri("/api/department") .retrieve() .bodyToMono(Department.class) .subscribe(dept -> { try (Tracer.SpanInScope scope = tracer.withSpanInScope(currentSpan)) { // 此处日志将包含traceId和spanId log.info("获取部门信息: {}", dept.getName()); } }); }
3. 验证日志格式配置
确保日志框架已配置traceId和spanId的输出占位符:
- Logback配置示例:
<encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{traceId},%X{spanId}] - %msg%n</pattern> </encoder>
- Log4j2配置示例:
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%X{traceId},%X{spanId}] - %msg%n"/>
注意事项
如果使用block()同步调用WebClient,追踪上下文会自动传递,无需手动处理;仅异步场景(如subscribe)需要手动传递上下文。
内容的提问来源于stack exchange,提问作者asu
相关产品推荐
相关产品推荐

