Spring RestClient报错时如何记录完整请求与响应?
解决方案:RestClient/WebClient 统一捕获错误并记录请求响应
核心思路
放弃直接使用getForObject/postForObject这类自动反序列化的方法,改为先获取原始响应字符串,再手动处理反序列化。这样既能捕获HTTP状态码错误、连接超时等请求异常,也能捕获JSON反序列化失败的异常,同时在同一位置记录完整的请求和响应数据,且仅在出错时记录。
RestClient 实现示例
封装一个通用工具类,统一处理请求、错误捕获和日志记录:
import org.springframework.http.*; import org.springframework.web.client.RestClient; import com.fasterxml.jackson.databind.ObjectMapper; public class RestClientErrorHandler { private final RestClient restClient; private final ObjectMapper objectMapper; public RestClientErrorHandler(RestClient restClient, ObjectMapper objectMapper) { this.restClient = restClient; this.objectMapper = objectMapper; } public <T> T sendRequest(String url, HttpMethod method, HttpHeaders headers, Object requestBody, Class<T> responseType) { // 提前序列化请求体,用于出错时记录 String reqBodyStr = null; if (requestBody != null) { try { reqBodyStr = objectMapper.writeValueAsString(requestBody); } catch (Exception e) { reqBodyStr = "序列化请求体失败: " + e.getMessage(); } } ResponseEntity<String> rawResponse; try { // 发送请求并获取原始响应字符串 rawResponse = restClient.method(method) .uri(url) .headers(h -> h.addAll(headers)) .body(requestBody) .retrieve() .toEntity(String.class); } catch (Exception e) { // 捕获请求过程中的异常(连接超时、DNS错误等) logError(url, method, reqBodyStr, null, e); throw e; } // 检查HTTP状态码是否正常 if (!rawResponse.getStatusCode().is2xxSuccessful()) { logError(url, method, reqBodyStr, rawResponse.getBody(), new RuntimeException("HTTP错误: " + rawResponse.getStatusCode())); throw new RuntimeException("HTTP错误: " + rawResponse.getStatusCode()); } // 尝试反序列化响应体 try { return objectMapper.readValue(rawResponse.getBody(), responseType); } catch (Exception e) { // 捕获反序列化异常 logError(url, method, reqBodyStr, rawResponse.getBody(), e); throw e; } } // 错误日志记录逻辑,可替换为SLF4J等日志框架 private void logError(String url, HttpMethod method, String reqBody, String respBody, Exception e) { System.err.println("=== 请求错误详情 ==="); System.err.println("请求地址: " + url); System.err.println("请求方法: " + method); System.err.println("请求体: " + reqBody); System.err.println("响应体: " + respBody); System.err.println("错误信息: " + e.getMessage()); System.err.println("堆栈跟踪: "); e.printStackTrace(); } }
使用方式:
// 初始化RestClient和ObjectMapper RestClient restClient = RestClient.create(); ObjectMapper objectMapper = new ObjectMapper(); RestClientErrorHandler handler = new RestClientErrorHandler(restClient, objectMapper); // 发送请求 HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); UserRequest req = new UserRequest("test", "test@example.com"); try { UserResponse resp = handler.sendRequest("https://api.example.com/user", HttpMethod.POST, headers, req, UserResponse.class); } catch (Exception e) { // 统一处理错误,日志已在工具类中记录 }
WebClient 实现示例
对于响应式的WebClient,逻辑类似,通过exchangeToMono先获取原始响应字符串:
import org.springframework.http.HttpMethod; import org.springframework.http.HttpHeaders; import org.springframework.web.reactive.function.client.WebClient; import com.fasterxml.jackson.databind.ObjectMapper; import reactor.core.publisher.Mono; public class WebClientErrorHandler { private final WebClient webClient; private final ObjectMapper objectMapper; public WebClientErrorHandler(WebClient webClient, ObjectMapper objectMapper) { this.webClient = webClient; this.objectMapper = objectMapper; } public <T> Mono<T> sendRequest(String url, HttpMethod method, HttpHeaders headers, Object requestBody, Class<T> responseType) { String reqBodyStr = null; if (requestBody != null) { try { reqBodyStr = objectMapper.writeValueAsString(requestBody); } catch (Exception e) { reqBodyStr = "序列化请求体失败: " + e.getMessage(); } } return webClient.method(method) .uri(url) .headers(h -> h.addAll(headers)) .bodyValue(requestBody) .exchangeToMono(response -> { // 先获取响应体字符串 return response.bodyToMono(String.class) .flatMap(respBody -> { if (!response.statusCode().is2xxSuccessful()) { logError(url, method, reqBodyStr, respBody, new RuntimeException("HTTP错误: " + response.statusCode())); return Mono.error(new RuntimeException("HTTP错误: " + response.statusCode())); } try { T result = objectMapper.readValue(respBody, responseType); return Mono.just(result); } catch (Exception e) { logError(url, method, reqBodyStr, respBody, e); return Mono.error(e); } }) .onErrorResume(e -> { // 捕获请求过程中的异常 logError(url, method, reqBodyStr, null, e); return Mono.error(e); }); }); } private void logError(String url, HttpMethod method, String reqBody, String respBody, Exception e) { System.err.println("=== 请求错误详情 ==="); System.err.println("请求地址: " + url); System.err.println("请求方法: " + method); System.err.println("请求体: " + reqBody); System.err.println("响应体: " + respBody); System.err.println("错误信息: " + e.getMessage()); System.err.println("堆栈跟踪: "); e.printStackTrace(); } }
关键优势
- 统一错误捕获:同一位置处理HTTP错误、请求异常、反序列化错误
- 按需记录:仅在出错时记录请求和响应,避免冗余日志
- 避免重复代码:封装后所有请求复用同一逻辑,无需每个请求手动处理字符串解析
内容的提问来源于stack exchange,提问作者Aleksander
相关产品推荐
相关产品推荐

