如何让Reactor Netty HttpClient一次性完整记录响应日志?
解决WebClient响应日志分块混乱的问题
要实现响应完全就绪后一次性记录完整日志,别用HttpClient的wiretap了——它是Reactor Netty底层IO层面的日志,天生就是按分块读取的时机输出,没法改成一次性输出。我们可以在WebClient层面自定义拦截器,手动收集完整响应后再打日志。
具体实现步骤
- 自定义
ExchangeFilterFunction,拦截请求和响应:- 让请求正常发送,拿到响应后读取完整的响应体内容
- 记录完整的请求和响应日志
- 把读取过的响应体重新封装回去(因为
DataBuffer是一次性消费的,必须缓存后再返回,否则后续业务拿不到响应体)
- 将自定义过滤器配置到WebClient中
代码示例
import org.springframework.core.io.buffer.DataBuffer; import org.springframework.core.io.buffer.DataBufferUtils; import org.springframework.http.HttpHeaders; 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 org.springframework.web.reactive.function.client.ExchangeFunction; import reactor.core.publisher.Mono; import java.nio.charset.StandardCharsets; public class FullResponseLoggingFilter implements ExchangeFilterFunction { @Override public Mono<ClientResponse> filter(ClientRequest request, ExchangeFunction next) { // 按需记录请求基础信息 String requestLog = String.format("请求方法: %s, 请求URL: %s", request.method(), request.url()); return next.exchange(request) .flatMap(response -> { // 读取完整响应体 return DataBufferUtils.join(response.body()) .map(dataBuffer -> { byte[] bodyBytes = new byte[dataBuffer.readableByteCount()]; dataBuffer.read(bodyBytes); DataBufferUtils.release(dataBuffer); String responseBody = new String(bodyBytes, StandardCharsets.UTF_8); // 输出完整响应日志 String responseLog = String.format("响应状态码: %s, 响应体: %s", response.statusCode(), responseBody); // 替换成你项目的日志框架(如SLF4J的logger) System.out.println(requestLog); System.out.println(responseLog); // 重新封装响应体,保证后续业务能正常获取 DataBuffer newBuffer = response.bufferFactory().wrap(bodyBytes); return ClientResponse.from(response) .body(Mono.just(newBuffer)) .build(); }); }); } }
配置WebClient时添加过滤器:
import org.springframework.web.reactive.function.client.WebClient; WebClient webClient = WebClient.builder() .filter(new FullResponseLoggingFilter()) // 其他配置(如baseUrl、编码等) .baseUrl("https://your-target-url.com") .build();
注意事项
- 如果响应体特别大(如几十MB以上),一次性读取到内存可能引发OOM,需根据业务场景评估是否适用,或添加长度限制(超过阈值则只记录部分内容)
- 日志输出请替换为项目实际使用的日志框架(如SLF4J),不要直接用
System.out
内容的提问来源于stack exchange,提问作者Deekshith Anand
相关产品推荐
相关产品推荐

