Spring RestTempate 打印请求和响应内容日志

系统中经常需要调用第三方接口实现业务功能,为了方便调试和定位问题,我们通常需要将接口调用参数和返回结果打印到日志文件中。在Spring项目中一般会用RestTemplate来调用第三方接口。
通过在RestTemplate调用过程中统一打印日志,可以保持代码的整洁,也可以统一日志格式,比在业务逻辑中到处打印接口调用日志要方便的多。

具体的代码参照 示例项目 https://github.com/qihaiyan/springcamp/tree/master/spring-rest-template-log

一、概述

RestTemplate使用前需要先定义bean,在定义bean时可以通过指定interceptors来打印日志。

二、定义RestTemplate的bean,并指定interceptors

RestTemplate的bean的定义在RestTemplateConfig类中实现。

RestTemplateConfig.java:

@Bean
    public RestTemplate restTemplate(RestTemplateBuilder builder) {
        return builder
                .requestFactory(() -> new HttpComponentsClientHttpRequestFactory(httpClient()))
                .interceptors(new CustomClientHttpRequestInterceptor())
                .build();
    }

其中 interceptors方法用来指定我们自己实现的日志打印 interceptors 。

三、实现日志打印 interceptors

自定义的interceptors需要实现 ClientHttpRequestInterceptor 这个 interface。

static class CustomClientHttpRequestInterceptor implements ClientHttpRequestInterceptor {
    @Override
    @NonNull
    public ClientHttpResponse intercept(HttpRequest request, @NonNull byte[] bytes, @NonNull ClientHttpRequestExecution execution) throws IOException {
        log.info("HTTP Method: {}, URI: {}, Headers: {}", request.getMethod(), request.getURI(), request.getHeaders());
        request.getMethod();
        if (request.getMethod().equals(HttpMethod.POST)) {
            log.info("HTTP body: {}", new String(bytes, StandardCharsets.UTF_8));
        }

        ClientHttpResponse response = execution.execute(request, bytes);
        ClientHttpResponse responseWrapper = new BufferingClientHttpResponseWrapper(response);

        String body = StreamUtils.copyToString(responseWrapper.getBody(), StandardCharsets.UTF_8);
        log.info("RESPONSE body: {}", body);

        return responseWrapper;
    }
}

接口请求地址和请求参数的日志打印比较简单,将intercept方法中的参与打印到日志中即可。

返回结果的body的日志打印需要做一些特殊处理。

ClientHttpResponse response = execution.execute(request, bytes);这行代码我们拿到了返回结果,但是不能直接读取返回的数据。

因为返回结果中的getBody()方法返回的是 InputStream ,直接读取后,会导致后续的处理拿不到结果。

因此我们需要对返回execution.execute方法返回的结果进行包装,将返回结果放到自定义的BufferingClientHttpResponseWrapper类中。

ClientHttpResponse responseWrapper = new BufferingClientHttpResponseWrapper(response);

BufferingClientHttpResponseWrapper类会将body的数据复制到一个本地变量中,用于支持多次读取。

BufferingClientHttpResponseWrapper类的实现如下:

static class BufferingClientHttpResponseWrapper implements ClientHttpResponse {

        private final ClientHttpResponse response;
        private byte[] body;

        BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
            this.response = response;
        }

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

        @NonNull
        public int getRawStatusCode() throws IOException {
            return this.response.getRawStatusCode();
        }

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

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

        @NonNull
        public InputStream getBody() throws IOException {
            if (this.body == null) {
                this.body = StreamUtils.copyToByteArray(this.response.getBody());
            }
            return new ByteArrayInputStream(this.body);
        }

        public void close() {
            this.response.close();
        }
    }

这个类主要是对 getBody()进行了特殊处理,在方法调用时,通过 treamUtils.copyToByteArray将body数据复制到本地变量中。

每次读取body时,都会从本地变量中读取,避免了第一次读取body后,后续再读取body会读不到数据的问题。

四、调用接口查看日志内容

我们在代码中模拟调用一个第三方接口 http://someservice/foo,接口调用在 DemoController 类中实现:

@GetMapping("/demo/get")
public Object demoGet(String arg) {
    return restTemplate.postForObject("http://someservice/foo", new BodyRequest("test"), BodyRequest.class);
}

在单元测试代码 DemoApplicationTest中调用这个接口。

String resp = testRestTemplate.getForObject("/demo/get?arg=test", String.class)

执行单元测试代码后,可以看到日志中打印的接口调用参数和返回结果:

HTTP Method: POST, URI: http://someservice/foo, Headers: [Accept:"application/json, application/*+json", Content-Type:"application/json", Content-Length:"15"]
HTTP body: {"arg1":"test"}
RESPONSE body: {"code": 200}

日志中打印的返回结果 {"code": 200}是我们在单元测试中对 http://someservice/foo接口mock的数据。

具体如何对第三方接口进行mock,可以参照 springboot单元测试技术 这篇文章。

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Spring Boot提供了一种简单的方式来使用过滤器来打印请求响应信息。 首先,我们需要创建一个实现了javax.servlet.Filter接口的过滤器类。可以在该类的doFilter方法中实现日志输出的逻辑。 ```java public class LoggingFilter implements Filter { private static final Logger LOGGER = LoggerFactory.getLogger(LoggingFilter.class); @Override public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { // 打印请求信息 HttpServletRequest request = (HttpServletRequest) servletRequest; LOGGER.info("Request: {} {} {}", request.getMethod(), request.getRequestURI(), request.getProtocol()); // 打印响应信息 ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper((HttpServletResponse) servletResponse); filterChain.doFilter(servletRequest, responseWrapper); byte[] responseContent = responseWrapper.getContentAsByteArray(); LOGGER.info("Response: {}", new String(responseContent, responseWrapper.getCharacterEncoding())); // 将响应内容写回 responseWrapper.copyBodyToResponse(); } // 其他方法 } ``` 接下来,我们需要在Spring Boot应用程序的配置类中注册该过滤器,使其生效。 ```java @Configuration public class WebConfig { @Bean public FilterRegistrationBean<LoggingFilter> loggingFilterRegistrationBean() { FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>(); registrationBean.setFilter(new LoggingFilter()); registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE); registrationBean.addUrlPatterns("/*"); return registrationBean; } } ``` 在上述代码中,我们通过`FilterRegistrationBean`来注册过滤器,并指定其优先级为最高。通过`addUrlPatterns`方法,我们可以指定过滤器要拦截的URL模式。 使用上述的过滤器配置后,每当有请求进入时,过滤器会拦截并打印请求信息;当响应返回时,过滤器也会打印相应的响应信息。这样可以方便地跟踪和调试请求响应的过程。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值