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

如何记录Spring 5 WebClient的REST请求与响应(含POST调用)?

嘿,作为刚接触Spring WebClient的新手,能理解你想把第三方REST服务的请求和响应完整记录下来的需求——毕竟调试接口、排查问题的时候,这些日志简直是救命神器!下面我给你分享几种最实用的实现方式,重点针对POST请求的场景:

方法一:自定义ExchangeFilterFunction(官方推荐的灵活方案)

WebClient自带的ExchangeFilterFunction是实现请求/响应日志最常用的方式,它能拦截请求发送前和响应返回后的所有细节,完全支持自定义日志格式。

步骤1:编写日志过滤器

你需要实现两个核心逻辑:记录请求(包括POST的请求体)、记录响应(包括响应体)。注意请求体和响应体都是一次性消费的数据流,直接读取后要缓存起来,避免后续流程无法获取数据。

import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.http.client.reactive.ClientHttpResponse;
import org.springframework.web.reactive.function.client.ExchangeFilterFunction;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.function.Predicate;

public class WebClientLoggingFilter {

    // 判断是否是可打印的文本类型(避免日志输出二进制数据)
    private static final Predicate<MediaType> IS_TEXT_TYPE = mediaType ->
            mediaType != null && (mediaType.isCompatibleWith(MediaType.APPLICATION_JSON) ||
                    mediaType.isCompatibleWith(MediaType.TEXT_PLAIN) ||
                    mediaType.isCompatibleWith(MediaType.APPLICATION_FORM_URLENCODED));

    // 记录请求日志
    public static ExchangeFilterFunction logRequest() {
        return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
            StringBuilder sb = new StringBuilder();
            sb.append("Sending request: ").append(clientRequest.method()).append(" ").append(clientRequest.url());
            // 记录请求头
            HttpHeaders headers = clientRequest.headers();
            if (!headers.isEmpty()) {
                sb.append("\nHeaders: ").append(headers);
            }
            // 记录POST/PUT请求体(仅文本类型)
            if (HttpMethod.POST.equals(clientRequest.method()) || HttpMethod.PUT.equals(clientRequest.method())) {
                clientRequest.body().map(dataBuffer -> {
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    DataBufferUtils.release(dataBuffer);
                    String body = new String(bytes, StandardCharsets.UTF_8);
                    if (IS_TEXT_TYPE.test(clientRequest.headers().getContentType())) {
                        sb.append("\nRequest body: ").append(body);
                    }
                    return bytes;
                }).subscribe();
            }
            // 这里替换成你项目的日志框架,比如SLF4J的log.debug
            System.out.println(sb.toString());
            return Mono.just(clientRequest);
        });
    }

    // 记录响应日志
    public static ExchangeFilterFunction logResponse() {
        return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
            StringBuilder sb = new StringBuilder();
            sb.append("Received response: ").append(clientResponse.statusCode());
            // 记录响应头
            HttpHeaders headers = clientResponse.headers().asHttpHeaders();
            if (!headers.isEmpty()) {
                sb.append("\nHeaders: ").append(headers);
            }
            // 记录响应体(仅文本类型)
            return clientResponse.body((dataBuffer, context) -> {
                byte[] bytes = new byte[dataBuffer.readableByteCount()];
                dataBuffer.read(bytes);
                DataBufferUtils.release(dataBuffer);
                String body = new String(bytes, StandardCharsets.UTF_8);
                if (IS_TEXT_TYPE.test(clientResponse.headers().contentType())) {
                    sb.append("\nResponse body: ").append(body);
                }
                // 这里替换成你项目的日志框架
                System.out.println(sb.toString());
                return Mono.just(context.bufferFactory().wrap(bytes));
            });
        });
    }
}

步骤2:将过滤器添加到WebClient

创建WebClient实例的时候,把上面的两个过滤器整合进去:

import org.springframework.web.reactive.function.client.WebClient;

public class WebClientConfig {
    public WebClient createWebClient() {
        return WebClient.builder()
                .baseUrl("https://第三方服务基础地址")
                .filter(WebClientLoggingFilter.logRequest())
                .filter(WebClientLoggingFilter.logResponse())
                .build();
    }
}
方法二:利用Spring Boot配置(快速开启基础日志)

如果你只需要基础的请求/响应元数据(比如URL、状态码、头信息),不想写自定义代码,可以直接在application.properties里加配置:

# 开启WebClient请求详情日志
spring.webclient.log-request-details=true

然后把日志级别调到DEBUG(比如在logback.xml里配置org.springframework.web.reactive.function.client的级别为DEBUG),就能看到框架自动输出的基础日志了。不过这种方式没法直接记录请求体和响应体,适合快速排查简单问题。

关键注意事项
  • 避免重复消费数据流:请求体和响应体都是DataBuffer类型,读取后必须释放并重新包装,否则后续的业务逻辑会因为数据流已被消费而报错,上面的示例代码已经处理了这个问题。
  • 过滤敏感信息:如果请求/响应里有密码、token等敏感数据,一定要在日志里过滤掉,比如替换成***,避免泄露。
  • 日志级别控制:日志量较大的时候,建议把日志级别设为DEBUG或TRACE,生产环境可以根据需求调整,避免影响性能。

这样配置后,你的POST请求的URL、请求头、请求体,还有响应的状态码、响应头、响应体都会被完整记录下来啦!

内容的提问来源于stack exchange,提问作者ddzz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 03:56:29