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

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();
    }
}

关键优势

  1. 统一错误捕获:同一位置处理HTTP错误、请求异常、反序列化错误
  2. 按需记录:仅在出错时记录请求和响应,避免冗余日志
  3. 避免重复代码:封装后所有请求复用同一逻辑,无需每个请求手动处理字符串解析

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.02 05:37:27