Spring Boot中WebClient的subscribe/onStatus方法日志缺失Trace ID
Spring Boot中WebClient的subscribe/onStatus日志缺失Trace ID问题
在Spring Boot应用中,WebClient调用的onStatus方法回调以及subscribe的回调内生成的日志无法显示Trace ID。已通过Micrometer配置让所有日志包含Trace ID,且WebClient附加的过滤器中日志能正常显示Trace ID,说明追踪配置功能正常,但上述特定位置日志仍缺失该ID。
环境信息
- Spring Boot版本:3.3.0
- Java版本:21
依赖配置
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-tracing-bridge-brave</artifactId> </dependency>
WebClient配置
@Bean public WebClient webclientBean(WebClient.Builder builder) throws SSLException { return builder .baseUrl(getBaseUrl()) .exchangeStrategies(ExchangeStrategies .builder() // 以下过滤器中的日志正常,包含trace id .filter(WebClientFilter.logRequest()) .filter(WebClientFilter.logResponse()) .filter(WebClientFilter.handleError()) .clientConnector(new ReactorClientHttpConnector(WebClientFilter.httpClient())) .build()); }
主类上下文传播配置
@PostConstruct public void init() { Hooks.enableAutomaticContextPropagation(); }
问题代码(WebClient调用逻辑)
public Object webClientDispatchPost(Object input, String endpoint) { return webClient.post().uri(endpoint).body(input != null ? BodyInserters.fromValue(input) : BodyInserters.empty()).retrieve() .onStatus(HttpStatusCode::is2xxSuccessful, (r) -> { log.info("trace id does not logged here !!!"); // do something }) .bodyToMono(Object.class) .subscribe(response -> { log.info("trace id does not logged here !!!"); }, error -> { log.info("trace id does not logged here !!!"); }, () -> log.info("trace id does not logged here !!!")); }
已尝试的解决方法
- 尝试在subscribe方法中访问MDC,但MDC为空(过滤器中可正常获取Trace ID)
- 添加
micrometer-tracing-bridge-otel依赖,无效果
解决方案
1. 避免手动调用subscribe(推荐)
将方法返回类型改为Mono<Object>,使用Reactor的doOnNext、doOnError、doOnComplete操作符替代subscribe的回调逻辑,由上层调用方(如Controller)负责订阅。这样Reactor上下文会自动传播,Trace ID可正常写入日志:
public Mono<Object> webClientDispatchPost(Object input, String endpoint) { return webClient.post() .uri(endpoint) .body(input != null ? BodyInserters.fromValue(input) : BodyInserters.empty()) .retrieve() .onStatus(HttpStatusCode::is2xxSuccessful, (r) -> { log.info("trace id should appear here now!!!"); // do something return Mono.empty(); }) .bodyToMono(Object.class) .doOnNext(response -> log.info("trace id should appear here now!!!")) .doOnError(error -> log.info("trace id should appear here now!!!")) .doOnComplete(() -> log.info("trace id should appear here now!!!")); }
2. 若必须手动订阅,手动传播上下文
通过Mono.deferContextual获取当前Reactor上下文,在subscribe回调中打开上下文作用域,确保MDC能捕获Trace ID:
public void webClientDispatchPost(Object input, String endpoint) { Mono<Object> mono = webClient.post() .uri(endpoint) .body(input != null ? BodyInserters.fromValue(input) : BodyInserters.empty()) .retrieve() .onStatus(HttpStatusCode::is2xxSuccessful, (r) -> { log.info("trace id should appear here now!!!"); return Mono.empty(); }) .bodyToMono(Object.class); mono.deferContextual(ctx -> { mono.subscribe( response -> { try (ContextView.Scope scope = ctx.openScope()) { log.info("trace id should appear here now!!!"); } }, error -> { try (ContextView.Scope scope = ctx.openScope()) { log.info("trace id should appear here now!!!"); } }, () -> { try (ContextView.Scope scope = ctx.openScope()) { log.info("trace id should appear here now!!!"); } } ); return Mono.empty(); }).subscribe(); }
3. 验证日志格式配置
确保日志框架(如Logback)的格式配置中包含Trace ID变量,示例Logback配置:
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg %X{traceId}%n</pattern> </encoder> </appender>
内容的提问来源于stack exchange,提问作者Ibrahim
相关产品推荐
相关产品推荐

