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

WebClient如何实现日志记录及4xx/5xx等各类错误响应处理

问题背景

当前采用同步模式使用WebClient调用多个外部API服务。当出现连接故障、SSL错误或任意4xx/5xx错误时,外部服务可能返回携带明确错误描述的响应体;本次需同时覆盖API错误、响应体为空的通信类错误两类场景。
实现目标:当外部服务调用失败时,按规则记录响应体与状态码,向本服务的调用方返回500错误形式的异常。

现存问题

需要实现在4xx/5xx调用失败时记录外部API响应体,同时记录外部API通信相关错误(包括外部服务端口变更、SSL连接类问题等),现需确认是否可同时实现异常信息与响应体的统一处理。

示例场景

外部服务返回401状态码时,响应体如下:

{
    "error": "invalid client certificate used"
}

现有代码

下述现有代码完全无法展示具体错误信息:

public String callExternalAPI() {
    final HttpStatus[] statuses = {HttpStatus.OK};
    try {
        String response =  webClientLogon.post()
            .uri(builder -> builder.path(myExternalServiceURL).build())
            .headers(this::myHeaders)
            .exchangeToMono(res ->{if (res.statusCode().equals(HttpStatus.OK)) {
                return res.bodyToMono(String.class);
            } else if (res.statusCode().isError()) {
                statuses[0] = res.statusCode();
                return res.bodyToMono(String.class);
            } else {
                statuses[0] = res.statusCode();
                return res.bodyToMono(String.class);
            }})
            .block();
        return handleResponse(response, statuses[0]);
    } catch (WebClientException ex){
        throw handleWebClientException(ex);
    }
}

实现方案

完全可以实现两类错误的统一处理,不需要用单元素数组中转存储状态码,直接在响应式流内完成分支判断即可,核心逻辑如下:

  • 在exchangeToMono阶段统一读取所有响应的body内容,通过defaultIfEmpty处理空响应场景,避免无响应体时抛出额外异常
  • 对2xx成功响应直接返回body内容,对4xx/5xx错误响应,直接组装携带状态码、响应体的自定义异常抛出,不将错误状态的响应传递到后续流程
  • 外层catch捕获所有WebClientException,既包含前面主动抛出的携带响应体的API调用异常,也包含连接失败、SSL错误、超时等无响应体的通信类异常,在catch块中统一完成日志记录、异常转换逻辑

修正后代码

// 自定义外部API调用异常,用于携带状态码和错误响应体
public class ExternalApiCallException extends RuntimeException {
    private final int httpStatus;
    private final String errorBody;

    public ExternalApiCallException(int httpStatus, String errorBody, String message) {
        super(message);
        this.httpStatus = httpStatus;
        this.errorBody = errorBody;
    }

    public int getHttpStatus() { return httpStatus; }
    public String getErrorBody() { return errorBody; }
}

public String callExternalAPI() {
    try {
        return webClientLogon.post()
                .uri(builder -> builder.path(myExternalServiceURL).build())
                .headers(this::myHeaders)
                .exchangeToMono(clientResponse -> {
                    HttpStatus status = clientResponse.statusCode();
                    // 统一读取响应体,空响应默认赋值为空字符串
                    return clientResponse.bodyToMono(String.class)
                            .defaultIfEmpty("")
                            .flatMap(responseBody -> {
                                if (status.is2xxSuccessful()) {
                                    return Mono.just(responseBody);
                                }
                                // 错误状态码直接抛出携带完整上下文的异常
                                return Mono.error(new ExternalApiCallException(
                                        status.value(),
                                        responseBody,
                                        String.format("外部API调用返回错误状态:%d", status.value())
                                ));
                            });
                })
                .block();
    } catch (WebClientException ex) {
        // 统一错误处理逻辑
        if (ex instanceof ExternalApiCallException apiEx) {
            // 处理4xx/5xx类有响应的错误,记录状态码+响应体
            log.error("外部API调用失败,状态码:{},响应体:{}", apiEx.getHttpStatus(), apiEx.getErrorBody(), ex);
        } else {
            // 处理连接、SSL、超时类无响应的通信错误,直接记录异常信息
            log.error("外部API通信失败,错误信息:{}", ex.getMessage(), ex);
        }
        // 统一转换为本服务的500错误返回给上游调用方
        throw new ResponseStatusException(HttpStatus.INTERNAL_SERVER_ERROR, "外部服务调用异常", ex);
    }
}

注意事项

  • 不要依赖数组、原子类等外部变量在响应式流里存状态,这种写法在并发场景下会出现状态串扰的问题,所有上下文都应该在响应式流内部传递
  • 读取错误响应体时必须处理空值场景,部分通信错误、网关拒绝的场景不会返回响应体,不做空处理会触发额外的序列化异常
  • 不需要单独配置WebClient的全局错误处理,在业务调用点统一处理更适配单接口的日志、异常转换需求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.02 02:46:12