如何用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,提问作者Влад Савостиков
相关产品推荐
相关产品推荐

