You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何记录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状态码,但看不到成功或错误日志。想请教两个问题:

  1. 怎么才能显示查询结果的日志?
  2. 当前的实现方式是否合理?

问题分析与解决

为什么看不到日志?

Mono是响应式流的一种,具有惰性执行特性——只有当流被订阅时,才会触发实际的API调用和后续的doOnSuccess/doOnError操作。你的控制器直接返回了Mono.just(new ResponseEntity(response, HttpStatus.OK)),而user这个流从未被订阅,所以createProUser的API调用根本没执行,自然不会输出任何日志。

当前实现的不合理之处

  1. 错误使用AtomicReference:响应式编程中应避免使用同步容器,而且你直接将response赋值为null,最终返回的响应体是空的,不符合业务逻辑。
  2. 潜在的阻塞风险:注释掉的user.toFuture().get()是阻塞调用,会破坏WebFlux的非阻塞特性,导致线程阻塞,引发性能问题。
  3. 流未被订阅:如前所述,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 22:02:47