如何记录Spring WebClient中Mono调用结果?排查无日志问题
问题描述
我尝试创建一个调用多个API的控制器,流程如下:
FIRST CALL IF SUCCESS CALL ANOTHER FIRST API CALL ANOTHER SECOND API ELSE RETURN API ERROR
原本想用Spring Cloud Gateway实现,但没找到对应功能,所以改用Spring控制器实现。
我写了调用首个API的方法:
public Mono<ProUserResponse> createProUser(UserCreationRequest request) { Function<UriBuilder, URI> uri = uriBuilder -> uriBuilder .path(userProperties.getCreateProUser().privateEndpoint()) .build(); HttpClient httpClient = HttpClient.create(); httpClient.wiretap("reactor.netty.http.client.HttpClient", LogLevel.DEBUG, ClientHttpConnector clientHttpConnector = new ReactorClientHttpConnector(httpClient); return WebClient.builder().clientConnector(clientHttpConnector).baseUrl(userProperties.getCreateProUser().baseUrl().toString()).build().post() .uri(uri) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .bodyValue(request) .retrieve() .bodyToMono(ProUserResponse.class) .retry(3); }
还有对应的控制器方法:
public Mono<ResponseEntity<MerchantCreationResponse>> createNewMerchant(@Valid @RequestBody MerchantCreationRequest merchantCreationRequest) throws MerchantCreationException, ExecutionException, InterruptedException { // Create User Mono<ProUserResponse> user = monolitheHttpClient.createProUser(merchantCreationRequest.merchant()).log(log.getName()); final AtomicReference<MerchantCreationResponse> response = null; //Long userId = user.toFuture().get().userid(); user.doOnSuccess(proUserResponse -> { log.info("youpi"); System.out.println("This is a message"); }).doOnError(throwable -> log.error("Failed for some reason", throwable)); return Mono.just(new ResponseEntity(response, HttpStatus.OK)); }
现在调用这个控制器时,请求返回200状态码,但看不到成功或错误日志。想请教两个问题:
- 怎么才能显示查询结果的日志?
- 当前的实现方式是否合理?
问题分析与解决
为什么看不到日志?
Mono是响应式流的一种,具有惰性执行特性——只有当流被订阅时,才会触发实际的API调用和后续的doOnSuccess/doOnError操作。你的控制器直接返回了Mono.just(new ResponseEntity(response, HttpStatus.OK)),而user这个流从未被订阅,所以createProUser的API调用根本没执行,自然不会输出任何日志。
当前实现的不合理之处
- 错误使用AtomicReference:响应式编程中应避免使用同步容器,而且你直接将
response赋值为null,最终返回的响应体是空的,不符合业务逻辑。 - 潜在的阻塞风险:注释掉的
user.toFuture().get()是阻塞调用,会破坏WebFlux的非阻塞特性,导致线程阻塞,引发性能问题。 - 流未被订阅:如前所述,
user流没有被触发,所有依赖它的操作都不会执行。
正确实现方式
1. 修正控制器逻辑,用响应式链式调用
核心思路是让控制器返回整个响应式流,由Spring WebFlux自动订阅触发执行,同时用flatMap处理成功后的后续API调用,onErrorResume处理错误场景:
public Mono<ResponseEntity<MerchantCreationResponse>> createNewMerchant(@Valid @RequestBody MerchantCreationRequest merchantCreationRequest) { // 调用第一个API,log方法会输出流的生命周期日志 return monolitheHttpClient.createProUser(merchantCreationRequest.merchant()) .log(log.getName()) // 第一个API成功后,并行调用另外两个API .flatMap(proUserResponse -> { // 假设这两个方法是你封装的另外两个API调用,返回Mono类型 Mono<Api1Response> api1Call = callAnotherFirstApi(proUserResponse); Mono<Api2Response> api2Call = callAnotherSecondApi(proUserResponse); // 合并两个API的结果,构造最终响应 return Mono.zip(api1Call, api2Call) .map(tuple -> { Api1Response res1 = tuple.getT1(); Api2Response res2 = tuple.getT2(); // 根据业务需求构造最终响应体 MerchantCreationResponse finalResponse = new MerchantCreationResponse(); finalResponse.setUserId(proUserResponse.userid()); // 填充其他字段... return new ResponseEntity<>(finalResponse, HttpStatus.OK); }); }) // 第一个API调用失败时,捕获错误并返回错误响应 .onErrorResume(throwable -> { log.error("创建Pro用户失败", throwable); return Mono.just(new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR)); }); }
2. 优化WebClient的创建
你的createProUser方法每次调用都会创建新的HttpClient和WebClient,会造成资源浪费。建议全局配置WebClient实例,注入使用:
配置类中创建WebClient Bean
@Configuration public class WebClientConfig { @Bean public WebClient userWebClient(UserProperties userProperties) { HttpClient httpClient = HttpClient.create() .wiretap("reactor.netty.http.client.HttpClient", LogLevel.DEBUG, AdvancedByteBufFormat.TEXTUAL); ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient); return WebClient.builder() .clientConnector(connector) .baseUrl(userProperties.getCreateProUser().baseUrl().toString()) .build(); } }
修改MonolitheHttpClient注入WebClient
private final WebClient userWebClient; private final UserProperties userProperties; // 构造方法注入 public MonolitheHttpClient(WebClient userWebClient, UserProperties userProperties) { this.userWebClient = userWebClient; this.userProperties = userProperties; } public Mono<ProUserResponse> createProUser(UserCreationRequest request) { Function<UriBuilder, URI> uri = uriBuilder -> uriBuilder .path(userProperties.getCreateProUser().privateEndpoint()) .build(); return userWebClient.post() .uri(uri) .accept(MediaType.APPLICATION_JSON) .contentType(MediaType.APPLICATION_JSON) .bodyValue(request) .retrieve() .bodyToMono(ProUserResponse.class) .retry(3); }
总结
- 看不到日志的核心原因是响应式流未被订阅,修正后让控制器返回完整的流即可触发执行并输出日志。
- 当前实现违背了响应式编程的非阻塞原则,改用链式调用+
flatMap/onErrorResume的方式才是合理的WebFlux实践。 - 全局复用WebClient实例能提升性能,避免不必要的资源创建。
内容的提问来源于stack exchange,提问作者Jérémie
相关产品推荐
相关产品推荐

