spring6 restClient如何打印请求和响应日志

472 阅读2分钟

在项目升级到springboot3.x后,官方提供的RestClient客户端是必须体验的。

那么,在实际生产中,我们经常会遇到一种情况,就行希望在日志里打印我们对第三方接口的请求参数和返回值,以便我们更好的调参和定位问题,那么,本文就让我们来了解一下,如何使用最新的spring6 restClient打印请求和响应日志。

开始

其实,和springboot2.x时代,实现方案一样,还是通过spring的ClientHttpRequestInterceptor扩展点来实现。

ClientHttpRequestInterceptor是一个函数式接口,很简单:

ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException;

其可以拦截RestClient对外请求的request请求体,body参数,以及发生的异常,同时还能拦截返回的Response结果。

所以,实现了这个接口,我们就可以同时获得请求实际发生时的参数情况和结果值了,打印日志的功能也就有了。

show code:

打印日志的实现类

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {

    private static final Logger logger = LoggerFactory.getLogger(LoggingRequestInterceptor.class);

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        // 打印请求信息
        logRequest(request, body);
        // 执行请求
        ClientHttpResponse response = new RepeatReadClientHttpRequestWrapper(execution.execute(request, body));
        // 打印响应信息
        logResponse(response);
        return response;
    }

    private void logRequest(HttpRequest request, byte[] body) {
        logger.debug("Request URI: {} {}",request.getMethod(), request.getURI());
        logger.debug("Request Headers: {}", request.getHeaders());
        if (body.length > 0) {
            logger.debug("Request Body: {}", new String(body, StandardCharsets.UTF_8));
        }
    }

    private void logResponse(ClientHttpResponse response) throws IOException {
        logger.debug("Response  Code: {} , Headers: {}", response.getStatusCode(),response.getHeaders());

        StringBuilder responseBody = new StringBuilder();
        try (BufferedReader reader = new BufferedReader(new InputStreamReader(response.getBody(), StandardCharsets.UTF_8))) {
            String line;
            while ((line = reader.readLine()) != null) {
                responseBody.append(line);
            }
        }
        if (responseBody.length() > 0) {
            logger.debug("Response Body: {}", responseBody.toString());
        }
    }


    /**
     * 返回的响应流只能读取一次,所以需要重新封装一个类,实现ClientHttpResponse接口,重写getBody方法,返回一个InputStream,
     */
    public class RepeatReadClientHttpRequestWrapper implements ClientHttpResponse {
        private ClientHttpResponse response;
        private byte[] bodyData = null;

        public RepeatReadClientHttpRequestWrapper(ClientHttpResponse response) {
            this.response = response;
        }

        @Override
        public HttpStatusCode getStatusCode() throws IOException {
            return response.getStatusCode();
        }

        @Override
        public String getStatusText() throws IOException {
            return response.getStatusText();
        }

        @Override
        public void close() {
            response.close();
        }

        @Override
        public InputStream getBody() throws IOException {
            if (Objects.isNull(bodyData)) {
                bodyData = response.getBody().readAllBytes();
            }
            return new ByteArrayInputStream(bodyData);
        }

        @Override
        public HttpHeaders getHeaders() {
            return response.getHeaders();
        }
    }
}

日志打印类实现后,还需要注入到RestClient客户端中才能实现,很简单:

还记得前文的RestClient的构建bean配置类吗?

RestClient restClient = RestClient.builder()
        .baseUrl("http://localhost:8080/")
        .requestInterceptor(new LoggingRequestInterceptor())  //注入日志打印类
        .build();

结束,下班!

相关阅读

spring6.x使用@HttpExchange注解调用第三方接口

我的主页

寒澈笔记