Spring RestTemplate - 如何启用请求/响应的完整调试/日志记录



我已经使用 Spring RestTemplate 一段时间了,当我尝试调试它的请求和响应时,我一直碰壁。我基本上希望看到与我在打开"详细"选项的情况下使用 curl 时看到的相同内容。例如:

curl -v http://twitter.com/statuses/public_timeline.rss

将显示发送的数据和接收的数据(包括标头、cookie 等(。

我检查了一些相关的帖子,例如:如何在春季休息模板中记录响应?但我还没有设法解决这个问题。

一种方法是实际更改 RestTemplate 源代码并在那里添加一些额外的日志记录语句,但我会发现这种方法确实是最后的手段。应该有某种方法可以告诉Spring Web Client/RestTemplate以更友好的方式记录所有内容。

我的目标是能够使用以下代码执行此操作:

restTemplate.put("http://someurl", objectToPut, urlPathValues);

然后在日志文件或控制台中获取相同类型的调试信息(与我使用 curl 获得的信息相同(。我相信这对于任何使用Spring RestTemplate并遇到问题的人来说都非常有用。使用 curl 来调试 RestTemplate 问题不起作用(在某些情况下(。

只是为了完成示例,完整实现ClientHttpRequestInterceptor跟踪请求和响应:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {
    final static Logger log = LoggerFactory.getLogger(LoggingRequestInterceptor.class);
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        traceRequest(request, body);
        ClientHttpResponse response = execution.execute(request, body);
        traceResponse(response);
        return response;
    }
    private void traceRequest(HttpRequest request, byte[] body) throws IOException {
        log.info("===========================request begin================================================");
        log.debug("URI         : {}", request.getURI());
        log.debug("Method      : {}", request.getMethod());
        log.debug("Headers     : {}", request.getHeaders() );
        log.debug("Request body: {}", new String(body, "UTF-8"));
        log.info("==========================request end================================================");
    }
    private void traceResponse(ClientHttpResponse response) throws IOException {
        StringBuilder inputStringBuilder = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), "UTF-8"));
        String line = bufferedReader.readLine();
        while (line != null) {
            inputStringBuilder.append(line);
            inputStringBuilder.append('n');
            line = bufferedReader.readLine();
        }
        log.info("============================response begin==========================================");
        log.debug("Status code  : {}", response.getStatusCode());
        log.debug("Status text  : {}", response.getStatusText());
        log.debug("Headers      : {}", response.getHeaders());
        log.debug("Response body: {}", inputStringBuilder.toString());
        log.info("=======================response end=================================================");
    }
}

然后使用BufferingClientHttpRequestFactoryLoggingRequestInterceptor实例化RestTemplate

RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(new LoggingRequestInterceptor());
restTemplate.setInterceptors(interceptors);

BufferingClientHttpRequestFactory是必需的,因为我们希望在拦截器和初始调用代码中使用响应正文。默认实现只允许读取一次响应正文。

在 Spring Boot 中,您可以通过在属性(或其他 12 因素方法(中设置它来获得完整的请求/响应

logging.level.org.apache.http=DEBUG

此输出

-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connecting to localhost/127.0.0.1:41827
-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connection established 127.0.0.1:39546<->127.0.0.1:41827
-DEBUG o.a.http.impl.execchain.MainClientExec   : Executing request POST /v0/users HTTP/1.1
-DEBUG o.a.http.impl.execchain.MainClientExec   : Target auth state: UNCHALLENGED
-DEBUG o.a.http.impl.execchain.MainClientExec   : Proxy auth state: UNCHALLENGED
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> POST /v0/users HTTP/1.1
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Type: application/json;charset=UTF-8
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Length: 56
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Host: localhost:41827
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Connection: Keep-Alive
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Accept-Encoding: gzip,deflate
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "POST /v0/users HTTP/1.1[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Type: application/json;charset=UTF-8[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Length: 56[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Host: localhost:41827[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Connection: Keep-Alive[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Accept-Encoding: gzip,deflate[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "{"id":null,"email":"xenoterracide@gmail.com","new":true}"

和响应

-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connecting to localhost/127.0.0.1:41827
-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connection established 127.0.0.1:39546<->127.0.0.1:41827
-DEBUG o.a.http.impl.execchain.MainClientExec   : Executing request POST /v0/users HTTP/1.1
-DEBUG o.a.http.impl.execchain.MainClientExec   : Target auth state: UNCHALLENGED
-DEBUG o.a.http.impl.execchain.MainClientExec   : Proxy auth state: UNCHALLENGED
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> POST /v0/users HTTP/1.1
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Type: application/json;charset=UTF-8
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Length: 56
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Host: localhost:41827
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Connection: Keep-Alive
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Accept-Encoding: gzip,deflate
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "POST /v0/users HTTP/1.1[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Type: application/json;charset=UTF-8[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Length: 56[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Host: localhost:41827[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Connection: Keep-Alive[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Accept-Encoding: gzip,deflate[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "[r][n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "{"id":null,"email":"xenoterracide@gmail.com","new":true}"

或者只是logging.level.org.apache.http.wire=DEBUG似乎包含所有相关信息

用一些代码扩展@hstoerr答案:


创建日志记录请求拦截器以记录请求响应

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {
    private static final Logger log = LoggerFactory.getLogger(LoggingRequestInterceptor.class);
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        ClientHttpResponse response = execution.execute(request, body);
        log(request,body,response);
        return response;
    }
    private void log(HttpRequest request, byte[] body, ClientHttpResponse response) throws IOException {
        //do logging
    }
}

设置休息模板

RestTemplate rt = new RestTemplate();
//set interceptors/requestFactory
ClientHttpRequestInterceptor ri = new LoggingRequestInterceptor();
List<ClientHttpRequestInterceptor> ris = new ArrayList<ClientHttpRequestInterceptor>();
ris.add(ri);
rt.setInterceptors(ris);
rt.setRequestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
最好的

办法是将logging.level.org.springframework.web.client.RestTemplate=DEBUG添加到application.properties文件中。

其他解决方案(如设置log4j.logger.httpclient.wire(并不总是有效,因为它们假设您使用log4j和 Apache HttpClient ,这并不总是正确的。

但请注意,此语法仅适用于最新版本的 Spring Boot。

您可以使用

spring-rest-template-logger 来记录RestTemplate HTTP 流量。

向 Maven 项目添加依赖项:

<dependency>
    <groupId>org.hobsoft.spring</groupId>
    <artifactId>spring-rest-template-logger</artifactId>
    <version>2.0.0</version>
</dependency>

然后按如下方式自定义RestTemplate

RestTemplate restTemplate = new RestTemplateBuilder()
    .customizers(new LoggingCustomizer())
    .build()

确保在 application.properties 中启用了调试日志记录:

logging.level.org.hobsoft.spring.resttemplatelogger.LoggingCustomizer = DEBUG

现在,所有 RestTemplate HTTP 流量都将在调试级别记录到 org.hobsoft.spring.resttemplatelogger.LoggingCustomizer

免责声明:我写了这个库。

xenoterracidide 给出的解决方案

logging.level.org.apache.http=DEBUG

很好,但问题是默认情况下不使用Apache HttpComponents。

要使用Apache HttpComponents添加到您的pom.xml

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpasyncclient</artifactId>
</dependency>

并配置RestTemplate

RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory());

这些答案实际上都没有解决100%的问题。 MJJ1409 获得了大部分内容,但方便地避免了记录响应的问题,这需要更多的工作。Paul Sabou提供了一个看似现实的解决方案,但没有提供足够的细节来实际实施(它对我根本不起作用(。Sofiene 获得了日志记录,但有一个关键问题:响应不再可读,因为输入流已被消耗!

我建议使用 BufferingClientHttpResponseWrapper 来包装响应对象,以允许多次读取响应正文:

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {
    private static final Logger logger = LoggerFactory.getLogger(LoggingRequestInterceptor.class);
    @Override
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
            final ClientHttpRequestExecution execution) throws IOException {
        ClientHttpResponse response = execution.execute(request, body);
        response = log(request, body, response);
        return response;
    }
    private ClientHttpResponse log(final HttpRequest request, final byte[] body, final ClientHttpResponse response) {
        final ClientHttpResponse responseCopy = new BufferingClientHttpResponseWrapper(response);
        logger.debug("Method: ", request.getMethod().toString());
        logger.debug("URI: ", , request.getURI().toString());
        logger.debug("Request Body: " + new String(body));
        logger.debug("Response body: " + IOUtils.toString(responseCopy.getBody()));
        return responseCopy;
    }
}

这不会消耗输入流,因为响应正文已加载到内存中,并且可以多次读取。如果您的类路径上没有 BufferingClientHttpResponseWrapper,则可以在此处找到简单的实现:

https://github.com/spring-projects/spring-android/blob/master/spring-android-rest-template/src/main/java/org/springframework/http/client/BufferingClientHttpResponseWrapper.java

对于设置休息模板:

LoggingRequestInterceptor loggingInterceptor = new LoggingRequestInterceptor();
restTemplate.getInterceptors().add(loggingInterceptor);

我终于找到了一种以正确的方式做到这一点的方法。大部分解决方案来自如何配置 Spring 和 SLF4J 以便我可以获取日志记录?

似乎有两件事需要做:

  1. 在 log4j.properties 中添加以下行: log4j.logger.httpclient.wire=DEBUG
  2. 确保 spring 不会忽略您的日志记录配置

第二个问题主要发生在使用 slf4j 的春季环境中(就像我的情况一样(。因此,当使用slf4j时,请确保发生以下两件事:

  1. 您的类路径中没有公共日志记录库:这可以通过在pom中添加排除描述符来完成:

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    
  2. log4j.properties文件存储在类路径中的某个地方,spring可以找到/看到它。如果您对此有问题,最后的解决方案是将 log4j.properties 文件放在默认包中(不是一个好的做法,只是为了看到事情按您的预期工作(

Logging RestTemplate

选项 1.打开调试日志记录。

配置休息模板

  • 默认情况下,RestTemplate 依赖于标准的 JDK 工具来建立 HTTP 连接。您可以切换到使用不同的HTTP库,例如Apache HttpComponents

    @Beanpublic RestTemplate restTemplate(RestTemplateBuilder builder( { RestTemplate restTemplate = builder.build((; 返回休息模板;}

配置日志记录

  • application.yml

    伐木: 水平: org.springframework.web.client.RestTemplate: DEBUG

选项 2.使用拦截器

包装器响应

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.StreamUtils;
public final class BufferingClientHttpResponseWrapper implements ClientHttpResponse {
    private final ClientHttpResponse response;
    private byte[] body;

    BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
        this.response = response;
    }
    public HttpStatus getStatusCode() throws IOException {
        return this.response.getStatusCode();
    }
    public int getRawStatusCode() throws IOException {
        return this.response.getRawStatusCode();
    }
    public String getStatusText() throws IOException {
        return this.response.getStatusText();
    }
    public HttpHeaders getHeaders() {
        return this.response.getHeaders();
    }
    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();
    }
}

实现拦截器

package com.example.logging;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
public class LoggingRestTemplate implements ClientHttpRequestInterceptor {
    private final static Logger LOGGER = LoggerFactory.getLogger(LoggingRestTemplate.class);
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body,
            ClientHttpRequestExecution execution) throws IOException {
        traceRequest(request, body);
        ClientHttpResponse response = execution.execute(request, body);
        return traceResponse(response);
    }
    private void traceRequest(HttpRequest request, byte[] body) throws IOException {
        if (!LOGGER.isDebugEnabled()) {
            return;
        }
        LOGGER.debug(
                "==========================request begin==============================================");
        LOGGER.debug("URI                 : {}", request.getURI());
        LOGGER.debug("Method            : {}", request.getMethod());
        LOGGER.debug("Headers         : {}", request.getHeaders());
        LOGGER.debug("Request body: {}", new String(body, "UTF-8"));
        LOGGER.debug(
                "==========================request end================================================");
    }
    private ClientHttpResponse traceResponse(ClientHttpResponse response) throws IOException {
        if (!LOGGER.isDebugEnabled()) {
            return response;
        }
        final ClientHttpResponse responseWrapper = new BufferingClientHttpResponseWrapper(response);
        StringBuilder inputStringBuilder = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(responseWrapper.getBody(), "UTF-8"));
        String line = bufferedReader.readLine();
        while (line != null) {
            inputStringBuilder.append(line);
            inputStringBuilder.append('n');
            line = bufferedReader.readLine();
        }
        LOGGER.debug(
                "==========================response begin=============================================");
        LOGGER.debug("Status code    : {}", responseWrapper.getStatusCode());
        LOGGER.debug("Status text    : {}", responseWrapper.getStatusText());
        LOGGER.debug("Headers            : {}", responseWrapper.getHeaders());
        LOGGER.debug("Response body: {}", inputStringBuilder.toString());
        LOGGER.debug(
                "==========================response end===============================================");
        return responseWrapper;
    }
}

配置休息模板

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    RestTemplate restTemplate = builder.build();
    restTemplate.setInterceptors(Collections.singletonList(new LoggingRestTemplate()));
    return restTemplate;
}

配置日志记录

  • 检查 LoggingRestTemplate 的包,例如在 application.yml 中:

    伐木: 水平: com.example.logging: DEBUG

选项 3.使用 httpcomponent

导入 httpcomponent 依赖项

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpasyncclient</artifactId>

配置休息模板

@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
    RestTemplate restTemplate = builder.build();
    restTemplate.setRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory());
    return restTemplate;
}

配置日志记录

  • 检查 LoggingRestTemplate 的软件包,例如在 application.yml 中:

    伐木:水平: org.apache.http: DEBUG

2019年7月----日 ----

(使用弹簧启动(

令我惊讶的是,Spring Boot 具有零配置的魔力,并没有提供一种简单的方法来使用 RestTemplate 检查或记录简单的 JSON 响应正文。我浏览了这里提供的各种答案和评论,并分享了我自己(仍然(有效的提炼版本,在我看来,考虑到当前的选项,这是一个合理的解决方案(我正在使用带有 Gradle 2.1.6 的 Spring Boot 4.4(

1. 使用提琴手作为 http 代理

这实际上是一个非常优雅的解决方案,因为它绕过了创建自己的拦截器或将底层 http 客户端更改为 apache 的所有繁琐工作(见下文(。

安装并运行提琴手

然后

-DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888添加到 VM 选项

2. 使用 Apache HttpClient

将 Apache HttpClient 添加到您的 Maven 或 Gradle 依赖项中。

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.9</version>
</dependency>

使用 HttpComponentsClientHttpRequestFactory 作为 RestTemplate 的请求工厂。最简单的方法是:

RestTemplate restTemplate = new RestTemplate();

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

application.properties文件中启用调试(如果您使用的是 Spring Boot(

logging.level.org.apache.http=DEBUG

如果您使用的是 Spring Boot,则需要确保设置了日志记录框架,例如,通过使用包含 Spring Boot starter 的依赖项spring-boot-starter-logging

3. 使用拦截器

我会让你通读其他答案和评论中的提案、反提案和陷阱,并自己决定是否要走这条路。

4. 日志 URL 和响应状态,不带正文

尽管这不符合记录正文的声明要求,但这是开始记录 REST 调用的一种快速简单的方法。它显示完整的 URL 和响应状态。

只需将以下行添加到您的application.properties文件中(假设您使用的是 Spring Boot,并且假设您使用的是包含 Spring Boot 的 Spring Boot 启动器依赖项spring-boot-starter-logging(

logging.level.org.springframework.web.client.RestTemplate=DEBUG

输出将如下所示:

2019-07-29 11:53:50.265 DEBUG o.s.web.client.RestTemplate : HTTP GET http://www.myrestservice.com/Endpoint?myQueryParam=myValue
2019-07-29 11:53:50.276 DEBUG o.s.web.client.RestTemplate : Accept=[application/json]
2019-07-29 11:53:50.584 DEBUG o.s.web.client.RestTemplate : Response 200 OK
2019-07-29 11:53:50.585 DEBUG o.s.web.client.RestTemplate : Reading to [org.mynamespace.MyJsonModelClass]

如其他响应中所述,响应正文需要特殊处理,以便可以重复读取(默认情况下,其内容在第一次读取时被消耗(。

拦截器本身可以包装响应并确保保留内容并可以重复读取(由记录器以及响应的使用者(,而不是在设置请求时使用BufferingClientHttpRequestFactory

我的拦截器,它

  • 使用包装器缓冲响应正文
  • 更紧凑的方式记录日志
  • 同时记录状态代码标识符(例如 201 已创建(
  • 包括请求序列号,允许轻松区分来自多个线程的并发日志条目

法典:

public class LoggingInterceptor implements ClientHttpRequestInterceptor {
    private final Logger log = LoggerFactory.getLogger(getClass());
    private AtomicInteger requestNumberSequence = new AtomicInteger(0);
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        int requestNumber = requestNumberSequence.incrementAndGet();
        logRequest(requestNumber, request, body);
        ClientHttpResponse response = execution.execute(request, body);
        response = new BufferedClientHttpResponse(response);
        logResponse(requestNumber, response);
        return response;
    }
    private void logRequest(int requestNumber, HttpRequest request, byte[] body) {
        if (log.isDebugEnabled()) {
            String prefix = requestNumber + " > ";
            log.debug("{} Request: {} {}", prefix, request.getMethod(), request.getURI());
            log.debug("{} Headers: {}", prefix, request.getHeaders());
            if (body.length > 0) {
                log.debug("{} Body: n{}", prefix, new String(body, StandardCharsets.UTF_8));
            }
        }
    }
    private void logResponse(int requestNumber, ClientHttpResponse response) throws IOException {
        if (log.isDebugEnabled()) {
            String prefix = requestNumber + " < ";
            log.debug("{} Response: {} {} {}", prefix, response.getStatusCode(), response.getStatusCode().name(), response.getStatusText());
            log.debug("{} Headers: {}", prefix, response.getHeaders());
            String body = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);
            if (body.length() > 0) {
                log.debug("{} Body: n{}", prefix, body);
            }
        }
    }
    /**
     * Wrapper around ClientHttpResponse, buffers the body so it can be read repeatedly (for logging & consuming the result).
     */
    private static class BufferedClientHttpResponse implements ClientHttpResponse {
        private final ClientHttpResponse response;
        private byte[] body;
        public BufferedClientHttpResponse(ClientHttpResponse response) {
            this.response = response;
        }
        @Override
        public HttpStatus getStatusCode() throws IOException {
            return response.getStatusCode();
        }
        @Override
        public int getRawStatusCode() throws IOException {
            return response.getRawStatusCode();
        }
        @Override
        public String getStatusText() throws IOException {
            return response.getStatusText();
        }
        @Override
        public void close() {
            response.close();
        }
        @Override
        public InputStream getBody() throws IOException {
            if (body == null) {
                body = StreamUtils.copyToByteArray(response.getBody());
            }
            return new ByteArrayInputStream(body);
        }
        @Override
        public HttpHeaders getHeaders() {
            return response.getHeaders();
        }
    }
}

配置:

 @Bean
    public RestTemplateBuilder restTemplateBuilder() {
        return new RestTemplateBuilder()
                .additionalInterceptors(Collections.singletonList(new LoggingInterceptor()));
    }

示例日志输出:

2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 >  Request: POST http://localhost:53969/payment/v4/private/payment-lists/10022/templates
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 >  Headers: {Accept=[application/json, application/json], Content-Type=[application/json;charset=UTF-8], Content-Length=[986]}
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 >  Body: 
{"idKey":null, ...}
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 <  Response: 200 OK 
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 <  Headers: {Content-Type=[application/json;charset=UTF-8], Transfer-Encoding=[chunked], Date=[Mon, 08 Oct 2018 08:58:53 GMT]}
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 <  Body: 
{ "idKey" : "10022", ...  }

除了其他答案中描述的 HttpClient 日志记录之外,您还可以引入一个 ClientHttpRequestInterceptor,它读取请求和响应的正文并记录它。如果其他内容也使用 HttpClient,或者您想要自定义日志记录格式,则可能需要执行此操作。注意:您需要为 RestTemplate 提供一个 BufferingClientHttpRequestFactory,以便您可以读取响应两次。

application.properties

logging.level.org.springframework.web.client=DEBUG

应用程序.yml

logging:
  level:  
    root: WARN
    org.springframework.web.client: DEBUG

这可能不是正确的方法,但我认为这是打印请求和响应的最简单方法,而无需在日志中填充太多内容。

通过在下面添加 2 行 application.properties 记录所有请求和响应,第一行用于记录请求,第二行用于记录响应。

logging.level.org.springframework.web.client.RestTemplate=DEBUG
logging.level.org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor=DEBUG

假设RestTemplate配置为使用 HttpClient 4.x,您可以在此处阅读 HttpClient 的日志记录文档。记录器与其他答案中指定的记录器不同。

此处提供了 HttpClient 3.x 的日志记录配置。

这里的许多响应都需要更改编码和自定义类,这确实没有必要。 Gte 一个调试代理,如 fiddler,并将您的 java 环境设置为在命令行(-Dhttp.proxyHost 和 -Dhttp.proxyPort(上使用代理,然后运行 fiddler,您可以完整地看到请求和响应。 还具有许多辅助优势,例如能够在提交修改服务器之前和之后修改结果和响应以运行实验。

可能出现的最后一个问题是,如果您必须使用HTTPS,则需要从Fiddler导出SSL证书并将其导入java密钥库(cacerts(提示:默认Java密钥库密码通常是"changeit"。

要在 Apache HttpClient 的帮助下登录 Logback

你需要类路径中的 Apache HttpClient:

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
  <version>4.5.10</version>
</dependency>

配置您的RestTemplate以使用 HttpClient:

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

要记录请求和响应,请添加到 Logback 配置文件:

<logger name="org.apache.http.wire" level="DEBUG"/>

或者记录更多:

<logger name="org.apache.http" level="DEBUG"/>

添加到上面的讨论中,这仅代表快乐的场景。 如果出现错误,您可能无法记录响应。

在这种情况下加上上述所有情况,您必须覆盖 DefaultResponseErrorHandler 并像下面一样设置它

restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());

奇怪的是,这些解决方案都不起作用,因为 RestTemplate 似乎不会在某些客户端和服务器 500x 错误时返回响应。在这种情况下,您还将通过实现 ResponseErrorHandler 来记录这些内容,如下所示。这是一个代码草案,但你明白了:

您可以设置与错误处理程序相同的拦截器:

restTemplate.getInterceptors().add(interceptor);
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
restTemplate.setErrorHandler(interceptor);

截距实现了两个接口:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus.Series;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.ResponseErrorHandler;
public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor, ResponseErrorHandler {
    static final Logger log = LoggerFactory.getLogger(LoggingRequestInterceptor.class);
    static final DefaultResponseErrorHandler defaultResponseErrorHandler = new DefaultResponseErrorHandler();
    final Set<Series> loggableStatuses = new HashSet();
    public LoggingRequestInterceptor() {
    }
    public LoggingRequestInterceptor(Set<Series> loggableStatuses) {
        loggableStatuses.addAll(loggableStatuses);
    }
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        this.traceRequest(request, body);
        ClientHttpResponse response = execution.execute(request, body);
        if(response != null) {
            this.traceResponse(response);
        }
        return response;
    }
    private void traceRequest(HttpRequest request, byte[] body) throws IOException {
        log.debug("===========================request begin================================================");
        log.debug("URI         : {}", request.getURI());
        log.debug("Method      : {}", request.getMethod());
        log.debug("Headers     : {}", request.getHeaders());
        log.debug("Request body: {}", new String(body, "UTF-8"));
        log.debug("==========================request end================================================");
    }
    private void traceResponse(ClientHttpResponse response) throws IOException {
        if(this.loggableStatuses.isEmpty() || this.loggableStatuses.contains(response.getStatusCode().series())) {
            StringBuilder inputStringBuilder = new StringBuilder();
            try {
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), "UTF-8"));
                for(String line = bufferedReader.readLine(); line != null; line = bufferedReader.readLine()) {
                    inputStringBuilder.append(line);
                    inputStringBuilder.append('n');
                }
            } catch (Throwable var5) {
                log.error("cannot read response due to error", var5);
            }
            log.debug("============================response begin==========================================");
            log.debug("Status code  : {}", response.getStatusCode());
            log.debug("Status text  : {}", response.getStatusText());
            log.debug("Headers      : {}", response.getHeaders());
            log.debug("Response body: {}", inputStringBuilder.toString());
            log.debug("=======================response end=================================================");
        }
    }
    public boolean hasError(ClientHttpResponse response) throws IOException {
        return defaultResponseErrorHandler.hasError(response);
    }
    public void handleError(ClientHttpResponse response) throws IOException {
        this.traceResponse(response);
        defaultResponseErrorHandler.handleError(response);
    }
}
如果您

使用的是任何ClientHttpRequestInterceptor,则使用BufferingClientHttpRequestFactory配置RestTemplate的技巧不起作用,如果您尝试通过拦截器登录,则会这样做。这是由于InterceptingHttpAccessor(RestTemplate子类(的工作方式。

长话短说。。。只需使用此类代替RestTemplate(请注意,这使用 SLF4J 日志记录 API,根据需要进行编辑(:

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
import javax.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.RestTemplate;
/**
 * A {@link RestTemplate} that logs every request and response.
 */
public class LoggingRestTemplate extends RestTemplate {
    // Bleh, this class is not public
    private static final String RESPONSE_WRAPPER_CLASS = "org.springframework.http.client.BufferingClientHttpResponseWrapper";
    private Logger log = LoggerFactory.getLogger(this.getClass());
    private boolean hideAuthorizationHeaders = true;
    private Class<?> wrapperClass;
    private Constructor<?> wrapperConstructor;
    /**
     * Configure the logger to log requests and responses to.
     *
     * @param log log destination, or null to disable
     */
    public void setLogger(Logger log) {
        this.log = log;
    }
    /**
     * Configure the logger to log requests and responses to by name.
     *
     * @param name name of the log destination, or null to disable
     */
    public void setLoggerName(String name) {
        this.setLogger(name != null ? LoggerFactory.getLogger(name) : null);
    }
    /**
     * Configure whether to hide the contents of {@code Authorization} headers.
     *
     * <p>
     * Default true.
     *
     * @param hideAuthorizationHeaders true to hide, otherwise false
     */
    public void setHideAuthorizationHeaders(boolean hideAuthorizationHeaders) {
        this.hideAuthorizationHeaders = hideAuthorizationHeaders;
    }
    /**
     * Log a request.
     */
    protected void traceRequest(HttpRequest request, byte[] body) {
        this.log.debug("xmit: {} {}n{}{}", request.getMethod(), request.getURI(), this.toString(request.getHeaders()),
          body != null && body.length > 0 ? "nn" + new String(body, StandardCharsets.UTF_8) : "");
    }
    /**
     * Log a response.
     */
    protected void traceResponse(ClientHttpResponse response) {
        final ByteArrayOutputStream bodyBuf = new ByteArrayOutputStream();
        HttpStatus statusCode = null;
        try {
            statusCode = response.getStatusCode();
        } catch (IOException e) {
            // ignore
        }
        String statusText = null;
        try {
            statusText = response.getStatusText();
        } catch (IOException e) {
            // ignore
        }
        try (final InputStream input = response.getBody()) {
            byte[] b = new byte[1024];
            int r;
            while ((r = input.read(b)) != -1)
                bodyBuf.write(b, 0, r);
        } catch (IOException e) {
            // ignore
        }
        this.log.debug("recv: {} {}n{}{}", statusCode, statusText, this.toString(response.getHeaders()),
          bodyBuf.size() > 0 ? "nn" + new String(bodyBuf.toByteArray(), StandardCharsets.UTF_8) : "");
    }
    @PostConstruct
    private void addLoggingInterceptor() {
        this.getInterceptors().add(new ClientHttpRequestInterceptor() {
            @Override
            public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
              throws IOException {
                // Log request
                if (LoggingRestTemplate.this.log != null && LoggingRestTemplate.this.log.isDebugEnabled())
                    LoggingRestTemplate.this.traceRequest(request, body);
                // Perform request
                ClientHttpResponse response = execution.execute(request, body);
                // Log response
                if (LoggingRestTemplate.this.log != null && LoggingRestTemplate.this.log.isDebugEnabled()) {
                    final ClientHttpResponse bufferedResponse = LoggingRestTemplate.this.ensureBuffered(response);
                    if (bufferedResponse != null) {
                        LoggingRestTemplate.this.traceResponse(bufferedResponse);
                        response = bufferedResponse;
                    }
                }
                // Done
                return response;
            }
        });
    }
    private ClientHttpResponse ensureBuffered(ClientHttpResponse response) {
        try {
            if (this.wrapperClass == null)
                this.wrapperClass = Class.forName(RESPONSE_WRAPPER_CLASS, false, ClientHttpResponse.class.getClassLoader());
            if (!this.wrapperClass.isInstance(response)) {
                if (this.wrapperConstructor == null) {
                    this.wrapperConstructor = this.wrapperClass.getDeclaredConstructor(ClientHttpResponse.class);
                    this.wrapperConstructor.setAccessible(true);
                }
                response = (ClientHttpResponse)this.wrapperConstructor.newInstance(response);
            }
            return response;
        } catch (Exception e) {
            this.log.error("error creating {} instance: {}", RESPONSE_WRAPPER_CLASS, e);
            return null;
        }
    }
    private String toString(HttpHeaders headers) {
        final StringBuilder headerBuf = new StringBuilder();
        for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
            if (headerBuf.length() > 0)
                headerBuf.append('n');
            final String name = entry.getKey();
            for (String value : entry.getValue()) {
                if (this.hideAuthorizationHeaders && name.equalsIgnoreCase(HttpHeaders.AUTHORIZATION))
                    value = "[omitted]";
                headerBuf.append(name).append(": ").append(value);
            }
        }
        return headerBuf.toString();
    }
}

我同意仅仅为了做到这一点就需要这么多工作是愚蠢的。

正如@MilacH指出的,实现中存在错误。如果返回状态代码> 400,则会从拦截器抛出 IOException,因为不会调用 errorHandler。可以忽略异常,然后在处理程序方法中再次捕获异常。

package net.sprd.fulfillment.common;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import static java.nio.charset.StandardCharsets.UTF_8;
public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {
    final static Logger log = LoggerFactory.getLogger(LoggingRequestInterceptor.class);
    @SuppressWarnings("HardcodedLineSeparator")
    public static final char LINE_BREAK = 'n';
    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        try {
            traceRequest(request, body);
        } catch (Exception e) {
            log.warn("Exception in LoggingRequestInterceptor while tracing request", e);
        }
        ClientHttpResponse response = execution.execute(request, body);
        try {
            traceResponse(response);
        } catch (IOException e) {
            // ignore the exception here, as it will be handled by the error handler of the restTemplate
            log.warn("Exception in LoggingRequestInterceptor", e);
        }
        return response;
    }
    private void traceRequest(HttpRequest request, byte[] body) {
        log.info("===========================request begin================================================");
        log.info("URI         : {}", request.getURI());
        log.info("Method      : {}", request.getMethod());
        log.info("Headers     : {}", request.getHeaders());
        log.info("Request body: {}", new String(body, UTF_8));
        log.info("==========================request end================================================");
    }
    private void traceResponse(ClientHttpResponse response) throws IOException {
        StringBuilder inputStringBuilder = new StringBuilder();
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), UTF_8))) {
            String line = bufferedReader.readLine();
            while (line != null) {
                inputStringBuilder.append(line);
                inputStringBuilder.append(LINE_BREAK);
                line = bufferedReader.readLine();
            }
        }
        log.info("============================response begin==========================================");
        log.info("Status code  : {}", response.getStatusCode());
        log.info("Status text  : {}", response.getStatusText());
        log.info("Headers      : {}", response.getHeaders());
        log.info("Response body: {}", inputStringBuilder);
        log.info("=======================response end=================================================");
    }
}

>org.apache.http.wire给出的日志太不可读,所以我使用日志来记录应用程序Servlet和RestTemplate请求和响应的有效负载。

build.gradle

compile group: 'org.zalando', name: 'logbook-spring-boot-starter', version: '2.6.2'

或 Maven 依赖项:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-spring-boot-starter</artifactId>
    <version>2.6.2</version>
</dependency>

application.properties(或槽式 YAML(:

logging.level.org.zalando.logbook = TRACE

RestTemplate.java

import java.util.function.Supplier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.zalando.logbook.httpclient.LogbookHttpRequestInterceptor;
import org.zalando.logbook.httpclient.LogbookHttpResponseInterceptor;
@Configuration
public class RestTemplateConfiguration {
    private final LogbookHttpRequestInterceptor logbookHttpRequestInterceptor;
    private final LogbookHttpResponseInterceptor logbookHttpResponseInterceptor;
    public RestTemplateConfiguration(LogbookHttpRequestInterceptor logbookHttpRequestInterceptor,
            LogbookHttpResponseInterceptor logbookHttpResponseInterceptor) {
        this.logbookHttpRequestInterceptor = logbookHttpRequestInterceptor;
        this.logbookHttpResponseInterceptor = logbookHttpResponseInterceptor;
    }
    @Bean
    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
        return restTemplateBuilder
                .requestFactory(new MyRequestFactorySupplier())
                .build();
    }
    class MyRequestFactorySupplier implements Supplier<ClientHttpRequestFactory> {
        @Override
        public ClientHttpRequestFactory get() {
            // Using Apache HTTP client
            CloseableHttpClient client = HttpClientBuilder.create()
                    .addInterceptorFirst(logbookHttpRequestInterceptor)
                    .addInterceptorFirst(logbookHttpResponseInterceptor)
                    .build();
            return new HttpComponentsClientHttpRequestFactory(client);
        }
    }
}

解决问题的简单方法:

  1. 使用 RestTemplateBuilder 创建 Bean of RestTemplate:它将使您能够更好地控制连接时间和读取时间。
@Configuration
public class RestTemplateConfig {
  @Bean
  public RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder
      .setConnectTimeout(Duration.ofMillis(60000))
      .setReadTimeout(Duration.ofMillis(60000))
      .build();
  }
}
  1. 将此行添加到resources/application.properties文件中logging.level.org.springframework.web.client.RestTemplate=DEBUG
    希望问题能得到解决!

现在最好的解决方案,只需添加依赖项:

<dependency>
  <groupId>com.github.zg2pro</groupId>
  <artifactId>spring-rest-basis</artifactId>
  <version>v.x</version>
</dependency>

它包含一个 LoggingRequestInterceptor 类,您可以通过这种方式添加到 RestTemplate:

通过以下方式将这个实用程序作为拦截器添加到 Spring RestTemplate 中来集成它:

restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build());

并将 SLF4j 实现添加到您的框架中,例如 Log4J。

或者直接使用"Zg2proRestTemplate"。@PaulSabou的"最佳答案"看起来如此,因为httpclient和所有apache.http库在使用Spring RestTemplate时不一定会加载。

也想添加我的实现。对于所有缺少的分号,我深表歉意,这是用Groovy写的。

我需要比提供的已接受答案更可配置的东西。这是一个非常敏捷的休息模板 bean,可以像 OP 正在寻找的那样记录所有内容。

自定义日志记录拦截器类:

import org.springframework.http.HttpRequest
import org.springframework.http.client.ClientHttpRequestExecution
import org.springframework.http.client.ClientHttpRequestInterceptor
import org.springframework.http.client.ClientHttpResponse
import org.springframework.util.StreamUtils
import java.nio.charset.Charset
class HttpLoggingInterceptor implements ClientHttpRequestInterceptor {
    private final static Logger log = LoggerFactory.getLogger(HttpLoggingInterceptor.class)
    @Override
    ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        logRequest(request, body)
        ClientHttpResponse response = execution.execute(request, body)
        logResponse(response)
        return response
    }
    private void logRequest(HttpRequest request, byte[] body) throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("===========================request begin================================================")
            log.debug("URI         : {}", request.getURI())
            log.debug("Method      : {}", request.getMethod())
            log.debug("Headers     : {}", request.getHeaders())
            log.debug("Request body: {}", new String(body, "UTF-8"))
            log.debug("==========================request end================================================")
        }
    }
    private void logResponse(ClientHttpResponse response) throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("============================response begin==========================================")
            log.debug("Status code  : {}", response.getStatusCode())
            log.debug("Status text  : {}", response.getStatusText())
            log.debug("Headers      : {}", response.getHeaders())
            log.debug("Response body: {}", StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()))
            log.debug("=======================response end=================================================")
        }
    }
}

休息模板 Bean 定义:

@Bean(name = 'myRestTemplate')
RestTemplate myRestTemplate(RestTemplateBuilder builder) {
    RequestConfig requestConfig = RequestConfig.custom()
            .setConnectTimeout(10 * 1000) // 10 seconds
            .setSocketTimeout(300 * 1000) // 300 seconds
            .build()
    PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager()
    connectionManager.setMaxTotal(10)
    connectionManager.closeIdleConnections(5, TimeUnit.MINUTES)
    CloseableHttpClient httpClient = HttpClients.custom()
            .setConnectionManager(connectionManager)
            .setDefaultRequestConfig(requestConfig)
            .disableRedirectHandling()
            .build()
    RestTemplate restTemplate = builder
            .rootUri("https://domain.server.com")
            .basicAuthorization("username", "password")
            .requestFactory(new BufferingClientHttpRequestFactory(new HttpComponentsClientHttpRequestFactory(httpClient)))
            .interceptors(new HttpLoggingInterceptor())
            .build()
    return restTemplate
}

实现:

@Component
class RestService {
    private final RestTemplate restTemplate
    private final static Logger log = LoggerFactory.getLogger(RestService.class)
    @Autowired
    RestService(
            @Qualifier("myRestTemplate") RestTemplate restTemplate
    ) {
        this.restTemplate = restTemplate
    }
    // add specific methods to your service that access the GET and PUT methods
    private <T> T getForObject(String path, Class<T> object, Map<String, ?> params = [:]) {
        try {
            return restTemplate.getForObject(path, object, params)
        } catch (HttpClientErrorException e) {
            log.warn("Client Error (${path}): ${e.responseBodyAsString}")
        } catch (HttpServerErrorException e) {
            String msg = "Server Error (${path}): ${e.responseBodyAsString}"
            log.error(msg, e)
        } catch (RestClientException e) {
            String msg = "Error (${path})"
            log.error(msg, e)
        }
        return null
    }
    private <T> T putForObject(String path, T object) {
        try {
            HttpEntity<T> request = new HttpEntity<>(object)
            HttpEntity<T> response = restTemplate.exchange(path, HttpMethod.PUT, request, T)
            return response.getBody()
        } catch (HttpClientErrorException e) {
            log.warn("Error (${path}): ${e.responseBodyAsString}")
        } catch (HttpServerErrorException e) {
            String msg = "Error (${path}): ${e.responseBodyAsString}"
            log.error(msg, e)
        } catch (RestClientException e) {
            String msg = "Error (${path})"
            log.error(msg, e)
        }
        return null
    }
}

请参阅 Q/A 以通过在 HttpInputStream 上启用多次读取来记录其余模板的请求和响应

为什么我的自定义客户端 HttpRequestInterceptor 具有空响应

我浏览了所有答案,如果您需要设置身份验证类型或连接超时,那么您可以这样做:

SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
factory.setConnectTimeout(Integer.valueOf(YOUR_VALUE));
factory.setReadTimeout(Integer.valueOf(YOUR_VALUE));
RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(factory));
restTemplate.getInterceptors().add(new LoggingRequestInterceptor());
restTemplate.getInterceptors().add(new BasicAuthenticationInterceptor(USER_NAME,PASSWORD,StandardCharsets.UTF_8));
return restTemplate;

当使用 RestTemplate 并且存在4xx5xx响应类型时,上述大多数解决方案都无法正常工作,因为ClientHttpResponse的正文为空。这是我用来在 RestTemplate 中记录整个 HTTP 请求/响应的解决方案,而不会在所有情况下丢失响应正文信息。Spring 引导版本<version>2.7.5</version>

1.创建LoggingInterceptor

import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import java.io.*;
@Component
@Slf4j
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
    traceRequest(request, body);
    ClientHttpResponse response = execution.execute(request, body);
    response = traceResponse(response);
    return response;
}
private void traceRequest(HttpRequest request, byte[] body) throws IOException {
    if (!log.isDebugEnabled()) {
        return;
    }
    log.debug("=========================== Request Begin ===========================");
    log.debug("URI          : " + request.getURI());
    log.debug("Method       : " + request.getMethod());
    log.debug("Headers      : " + request.getHeaders());
    log.debug("Body : " + new String(body, "utf-8"));
    log.debug("============================ Request End ============================");
}
private ClientHttpResponse traceResponse(ClientHttpResponse response) throws IOException {
    if (!log.isDebugEnabled()) {
        return response;
    }
    ClientHttpResponse newCopiedResponse = new BufferingClientHttpResponseWrapper(response);
    StringBuilder inputStringBuilder = new StringBuilder();
    // ClientHttpResponse there is no body in response in case of 4xx or 5xx code, so we skip the body part
    if (isSuccessStatus(response.getRawStatusCode())) {
        inputStringBuilder = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(newCopiedResponse.getBody(), "UTF-8"));
        String line = bufferedReader.readLine();
        while (line != null) {
            inputStringBuilder.append(line);
            line = bufferedReader.readLine();
        }
    }
    log.debug("=========================== Response Begin ===========================");
    log.debug("Status code   : {}", response.getStatusCode());
    log.debug("Status text   : {}", response.getStatusText());
    log.debug("Headers       : {}", response.getHeaders());
    if (isSuccessStatus(response.getRawStatusCode())) {
        log.debug("Response Body : {}", inputStringBuilder.toString());
        log.debug("============================ Response End ============================");
    }
    return newCopiedResponse;
}
private static boolean isSuccessStatus(int statusCode) {
    return (statusCode / 100) == 2;
}

/**
 * Wrapper around ClientHttpResponse, buffers the body so it can be read repeatedly (for logging & consuming the result).
 */
private static class BufferingClientHttpResponseWrapper implements ClientHttpResponse {
    private final ClientHttpResponse response;
    private byte[] body;
    public BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
        this.response = response;
    }
    @Override
    public InputStream getBody() throws IOException {
        if (body == null) {
            body = StreamUtils.copyToByteArray(response.getBody());
        }
        return new ByteArrayInputStream(body);
    }
    @Override
    public HttpStatus getStatusCode() throws IOException {
        return this.response.getStatusCode();
    }
    @Override
    public int getRawStatusCode() throws IOException {
        return this.response.getRawStatusCode();
    }
    @Override
    public String getStatusText() throws IOException {
        return this.response.getStatusText();
    }
    @Override
    public HttpHeaders getHeaders() {
        return this.response.getHeaders();
    }
    @Override
    public void close() {
        this.response.close();
    }
}

}

2.将其连接到RestTemplate

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate createRestTemplate(LoggingInterceptor loggingInterceptor) {
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setInterceptors(Collections.singletonList(loggingInterceptor));
        return restTemplate;
    }
}

3.在应用程序中应用适当的级别

logging:
  level:
    com:
      test: DEBUG

与使用 ClientHttpInterceptor 的响应相关,我找到了一种在没有缓冲工厂的情况下保留整个响应的方法。只需使用一些 utils 方法将响应正文输入流存储在字节数组中,该方法将从 body 复制该数组,但重要的是,用 try catch 包围此方法,因为如果响应为空(这是资源访问异常的原因(,它会中断,并且在 catch 中只是创建空字节数组,而不仅仅是使用该数组和原始响应中的其他参数创建 ClientHttpResponse 的匿名内部类。然后,您可以将新的 ClientHttpResponse 对象返回到其余模板执行链,并且可以使用以前存储的正文字节数组记录响应。这样,您将避免在实际响应中使用输入流,并且可以按原样使用Rest Template响应。请注意,如果您的响应太大,这可能会很危险

我的记录器配置使用了 xml

<logger name="org.springframework.web.client.RestTemplate">
    <level value="trace"/>
</logger>

然后你会得到如下的东西:

DEBUG org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:92) : Reading [com.test.java.MyClass] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@604525f1]

通过HttpMessageConverterExtractor.java:92,你需要继续调试,就我而言,我得到了这个:

genericMessageConverter.write(requestBody, requestBodyType, requestContentType, httpRequest);

而这个:

outputMessage.getBody().flush();

outputMessage.getBody(( 包含 http(post type( 发送的消息

最新更新