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

如何用WebClient自定义日志记录HttpRequest/HttpResponse请求响应体

解决方案:用WebClient的ExchangeFilterFunction拦截并记录请求/响应

要在请求实际发送、响应接收的节点记录日志,不能在Reactor管道构建阶段打日志(此时只是定义逻辑,未触发实际网络请求),正确的做法是使用ExchangeFilterFunction,它能在请求发送前、响应接收后拦截处理,对应实际的网络操作时刻。

步骤1:定义日志拦截过滤器

这个过滤器会分别处理请求发送前和响应接收后的日志记录,同时注意缓存请求/响应体,避免数据流被消耗:

import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.ClientResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import reactor.core.publisher.Mono;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

private static final Logger log = LoggerFactory.getLogger(YourClassName.class);

private ExchangeFilterFunction requestResponseLogger() {
    // 处理请求日志:实际发送前记录
    ExchangeFilterFunction requestFilter = ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
        StringBuilder logMsg = new StringBuilder("Send request. Headers: ").append(clientRequest.headers())
                .append(", URI: ").append(clientRequest.url());
        
        // 从请求属性中获取提前存入的请求体(避免直接读取body消耗数据流)
        Object requestBody = clientRequest.attribute("requestBody").orElse("无法解析请求体");
        logMsg.append(", Body: ").append(requestBody);
        log.info(logMsg.toString());
        
        return Mono.just(clientRequest);
    });

    // 处理响应日志:接收后记录
    ExchangeFilterFunction responseFilter = ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
        // 缓存响应体,记录后再放回响应中供后续使用
        return clientResponse.bodyToMono(String.class)
                .flatMap(body -> {
                    log.info("Received response. Status: {}, Headers: {}, Body: {}",
                            clientResponse.statusCode(), clientResponse.headers(), body);
                    // 重新构建响应,将缓存的body注入
                    return Mono.just(ClientResponse.from(clientResponse)
                            .body(body)
                            .build());
                });
    });

    // 组合请求和响应过滤器
    return requestFilter.andThen(responseFilter);
}

步骤2:为WebClient添加过滤器

创建WebClient实例时,将上述过滤器加入:

WebClient webClient = WebClient.builder()
        .filter(requestResponseLogger())
        .build();

步骤3:发送请求时传入请求体属性

在构建请求时,将请求体存入请求属性,供过滤器读取:

// 假设你的请求体对象
YourRequestDTO requestBody = new YourRequestDTO();
// 设置请求体内容...

HttpHeaders customHeaders = new HttpHeaders();
customHeaders.add("Content-Type", "application/json");
// 设置其他请求头...

return webClient.post()
        .uri("/your-api-path")
        .headers(headers -> headers.addAll(customHeaders))
        .attribute("requestBody", requestBody) // 将请求体存入属性
        .bodyValue(requestBody)
        .retrieve()
        .bodyToMono(YourResponseDTO.class);

关键说明

  • 为什么之前的方式无效:Reactor是惰性执行模型,管道构建阶段的log.info只是定义了日志逻辑,只有当订阅者触发订阅时(比如调用block()或subscribe()),才会执行实际的网络请求,所以之前的日志时机不对。
  • 请求体处理注意点:如果请求体是流式数据(如InputStream),需要用DataBufferUtils缓存后再转换为字符串记录,避免数据流被一次性消耗。

内容的提问来源于stack exchange,提问作者Влад Савостиков

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.13 07:10:31