【问题标题】:Spring RestTemplate - how to enable full debugging/logging of requests/responses?Spring RestTemplate - 如何启用请求/响应的完整调试/日志记录?
【发布时间】:2011-12-18 14:59:53
【问题描述】:

我使用 Spring RestTemplate 已经有一段时间了,当我尝试调试它的请求和响应时,我总是碰壁。我基本上希望看到与打开“详细”选项使用 curl 时看到的相同的东西。例如:

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

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

我查看了一些相关的帖子,例如: How do I log response in Spring RestTemplate? 但我还没有设法解决这个问题。

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

我的目标是能够使用如下代码做到这一点:

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

然后在日志文件或控制台中获得相同类型的调试信息(就像我使用 curl 获得的那样)。 我相信这对于使用 Spring RestTemplate 并遇到问题的任何人都非常有用。使用 curl 来调试您的 RestTemplate 问题是行不通的(在某些情况下)。

【问题讨论】:

  • 警告任何在 2018 年阅读的人:对此没有简单的答案!
  • 最简单的方法是在 AbstractHttpMessageConverter 类的 write(...) 方法中使用断点,有一个 outputMessage 对象,您可以在其中看到数据。附:您可以复制该值,然后使用在线格式化程序对其进行格式化。
  • 似乎这在 Spring 中应该很容易做到,但是,从这里的答案来看 - 并非如此。因此,另一种解决方案是完全绕过 Spring 并使用 Fiddler 之类的工具来捕获请求/响应。
  • 2019 年 7 月:由于这个问题仍然没有简单的解决方案,我试图在 my own answer below 中总结其他 24 个答案(到目前为止)及其 cmets 和讨论。希望对您有所帮助。

标签: java debugging logging resttemplate


【解决方案1】:

只是用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 是必需的,因为我们希望在拦截器和初始调用代码中都使用响应体。默认实现只允许读取一次响应正文。

【讨论】:

  • 这是错误的。如果您读取流,应用程序代码将无法读取响应。
  • 我们为 RestTemplate 提供了一个 BufferingClientHttpRequestFactory,因此我们可以读取两次响应。
  • 我们已经使用这种技术大约 3 个月了。正如@sofienezaghdoudi 所暗示的,它仅适用于配置了BufferingClientHttpResponseWrapper 的RestTemplate。但是,在使用 spring 的 mockServer 框架进行测试时,它不起作用,因为 MockRestServiceServer.createServer(restTemplate) 将 RequestFactory 覆盖为 InterceptingClientHttpRequestFactory
  • 技术好,实现是错误的。 404 情况下,response.getBody() 抛出 IOException -> 你永远不会得到日志,更糟糕的是,它将在你的进一步代码中成为 ResourceAccessException,而不是 RestClientResponseException
  • 感谢您的回复。但拥有多个“log.debug”是不好的做法,因为它可能分布在许多其他日志上。最好使用单个 log.debug 指令,这样您就可以确定所有内容都在同一个地方
【解决方案2】:

在 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,它似乎包含所有相关信息

【讨论】:

  • 这是我想做的最简单的事情。我强烈建议在接受的答案中包含这一点。
  • 根据RestTemplate的javadoc:by default the RestTemplate relies on standard JDK facilities to establish HTTP connections. You can switch to use a different HTTP library such as Apache HttpComponents
  • RestTemplate 默认不使用这些 Apache 类,正如@OrtomalaLokni 指出的那样,因此除了如何打印调试信息之外,您还应该包括 how 来使用它们当它们被使用时。
  • @ParthaSarathiGhosh 内容可能是 gzip 编码的,这就是您看不到原始文本的原因。
  • 如果您的应用配置为使用 Apache,这个简单的解决方案就可以工作
【解决方案3】:

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


创建 LoggingRequestInterceptor 以记录请求响应

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

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

【讨论】:

  • 这个直到 spring-3.1 版本才可用。
  • 它不回答“记录响应”的问题,而是留下 //do logging 评论。
  • 进行日志记录很容易,但这仅适用于请求,我看不到响应主体,假设我有响应对象,但读取它的流并不是一个好主意。
  • @PavelNiedoba BufferClientHttpRequestFactory 允许多次读取响应。
  • 如果您需要将有关请求/响应的信息存储在数据库中以进行调试并且常规日志记录不适合您的需要,这很有效。
【解决方案4】:

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

设置log4j.logger.httpclient.wire 等其他解决方案并不总是有效,因为它们假定您使用log4j 和Apache HttpClient,但并非总是如此。

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

【讨论】:

  • 这不是记录请求和响应正文,只是记录 url 和请求类型(spring-web-4.2.6)
  • 你是对的,它不是wire日志,它只包含url、resepone代码、POST参数等基本信息。
  • 你真正想要的是这个stackoverflow.com/a/39109538/206466
  • 这很好,但无法看到响应正文!
  • 太棒了。虽然它不打印响应正文,但它仍然非常有用。谢谢。
【解决方案5】:

您可以使用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

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

【讨论】:

  • 为什么这个答案被否决了?它帮助了我。谢谢,@Mark Hobson。
  • 很高兴它帮助了@RaffaelBecharaRameh。它最初被否决,因为我没有嵌入链接项目的说明。如果您觉得有用,请随时点赞!
  • 你支持 Gradle 吗?
  • @BlackHatSamurai spring-rest-template-logger 是一个常规的 Maven 工件,所以它应该可以在 Gradle 上正常工作。
  • 嗨@erhanasikoglu,不客气!没错,你可以在这里看到它的使用:github.com/markhobson/spring-rest-template-logger/blob/master/…
【解决方案6】:

异草胺给出的解决方法

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

【讨论】:

  • 最简单的方法,我只会补充一点,它不适用于 MockRestServiceServer,因为它会覆盖 requestFactory。
  • 运行良好,配置少问题!
【解决方案7】:

这些答案都没有真正解决 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;
    }

}

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

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

用于设置 RestTemplate:

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

【讨论】:

  • 相同,responseCopy.getBody() 在 404 的情况下抛出 IOexception,因此您永远不会将响应发送回您的进一步代码,并且通常 RestClientResponseException 成为 ResourceAccessException
  • 你应该在responseCopy.getBody()之前检查status==200
  • 但它是包私有的。您是否将 LoggingRequestInterceptor 放入包“org.springframework.http.client”中?
  • asyncRestTemplate 呢?当你拦截它时,它需要返回一个ListenableFuture,这是不可能在回调中用BufferingClientHttpResponseWrapper改变的。
  • @ÖmerFarukAlmalı 在这种情况下,您将需要使用链或转换,具体取决于您使用的番石榴版本。见:stackoverflow.com/questions/8191891/…
【解决方案8】:

我终于找到了一种正确的方法。 大部分解决方案来自 How do I configure Spring and SLF4J so that I can get logging?

看来有两件事需要做:

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

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

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

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

【讨论】:

  • 这对我不起作用,我做了这两件事。我不明白为什么在我的项目中无论如何都没有使用 log4j.properties 时需要放置它(通过 mvn 依赖项检查:tree)
  • 这对我也不起作用。我什至尝试将根记录器设置为调试模式,但仍然没有。
  • "httpclient.wire.content" 和 "httpclient.wire.header" 是来自 Axis2 框架的记录器名称。它们可用于记录,例如Spring 项目中的 SOAP 请求如果这些请求是使用 Axis2 完成的。
  • httpclient.wire 实际上来自 Apache HttpComponents HttpClient 库(请参阅hc.apache.org/httpcomponents-client-ga/logging.html)。此技术仅在您将 RestTemplate 配置为使用 HttpComponentsClientHttpRequestFactory 时才有效
【解决方案9】:

记录 RestTemplate

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

配置 RestTemplate

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

    @豆 公共 RestTemplate restTemplate(RestTemplateBuilder builder) { RestTemplate restTemplate = builder.build(); 返回休息模板; }

配置日志记录

  • application.yml

    记录: 等级: org.springframework.web.client.RestTemplate:调试

选项 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;
    }

}

配置 RestTemplate

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

配置日志记录

  • 查看LoggingRestTemplate的包,例如application.yml:

    记录: 等级: com.example.logging:调试

选项 3. 使用 httpcomponent

导入httpcomponent依赖

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

配置 RestTemplate

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

配置日志记录

  • 查看LoggingRestTemplate的包,例如application.yml:

    记录: 等级: org.apache.http:调试

【讨论】:

  • 请注意:如果要配置TestRestTemplate,请配置RestTemplateBuilder:@Bean public RestTemplateBuilder restTemplateBuilder() { return new RestTemplateBuilder().additionalInterceptors(Collections.singletonList(new LoggingRestTemplate( ))); }
  • 还要注意 new InputStreamReader(responseWrapper.getBody(), StandardCharsets.UTF_8));如果“另一端”返回错误,则可以抛出错误。您可能希望将其放入 try 块中。
【解决方案10】:

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

(使用 Spring Boot)

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

1。使用 Fiddler 作为 http 代理

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

安装并运行Fiddler

然后

-DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888 添加到您的虚拟机选项

2。使用 Apache HttpClient

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

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

使用HttpComponentsClientHttpRequestFactory 作为 RestTemplate 的 RequestFactory。最简单的方法是:

RestTemplate restTemplate = new RestTemplate();

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

在您的 application.properties 文件中启用 DEBUG(如果您使用的是 Spring Boot)

logging.level.org.apache.http=DEBUG

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

3。使用拦截器

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

4。不带正文记录 URL 和响应状态

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

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

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]

【讨论】:

  • 没有。 4 是最简单的调试方式。
  • 没有。 2为我工作。它记录请求的正文。谢谢!
  • 当我遇到这个问题时,我发现 No. 3 是一种简单的方法。
【解决方案11】:

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

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

我的拦截器,它

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

代码:

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", ...  }

【讨论】:

  • 此款与 2019 年春季版配合使用,保持机身完整。
  • 适用于 Spring 2.1.10 :) 谢谢
【解决方案12】:

除了in the other answer 中描述的 HttpClient 日志记录之外,您还可以引入一个 ClientHttpRequestInterceptor 读取请求和响应的正文并记录它。如果其他东西也使用 HttpClient,或者如果您想要自定义日志记录格式,您可能想要这样做。注意:您需要为 RestTemplate 提供一个 BufferingClientHttpRequestFactory 以便您可以读取两次响应。

【讨论】:

    【解决方案13】:

    application.properties

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

    application.yml

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

    【讨论】:

      【解决方案14】:

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

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

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

      【讨论】:

      • 记录响应对我不起作用。它只是记录状态码。它应该记录有效载荷吗?
      • 类 HttpEntityMethodProcessor (v5.1.8) 不记录任何内容。
      【解决方案15】:

      假设 RestTemplateconfigured 以使用 HttpClient 4.x,您可以阅读 HttpClient 的日志记录文档 here。记录器与其他答案中指定的记录器不同。

      HttpClient 3.x 的日志记录配置可用here

      【讨论】:

        【解决方案16】:

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

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

        【讨论】:

        • 这对我有用 intellij 和 fiddle 的常规安装。我编辑了运行配置并将 VM 选项设置为 -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888
        • 谢谢!与编写自己的拦截器相比,这是一个非常优雅的解决方案。
        • 是的,这是我们前进的方向,并且还没有回到使用 Spring / 拦截器 / 日志记录等的混乱状态。虽然不是 Fiddler - 我们一直在使用 TCP/IP Monitor eclipse 插件,但 IntelliJ 中可能有类似的东西。对我来说,Fiddler 很棒,但会导致证书、VPN 出现各种问题,因此,根据您的环境,它可能不是理想的解决方案。
        【解决方案17】:

        在 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"/>
        

        【讨论】:

        • 什么logback配置文件?
        • @G_V logback.xml 或 logback-test.xml 用于测试。
        • 它现在也适用于您的application.properties 中的org.apache.http.wire=DEBUG
        • @G_V 如果您使用的是 Spring-Boot。我的答案在没有 Boot 的情况下有效。
        【解决方案18】:

        除了上面的讨论之外,这仅代表快乐的场景。如果出现错误,您可能无法记录响应。

        在这种情况下加上上述所有情况,您必须覆盖 DefaultResponseErrorHandler 并将其设置如下

        restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());
        

        【讨论】:

          【解决方案19】:

          奇怪的是,这些解决方案都不起作用,因为 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);
              }
          }
          

          【讨论】:

          • 如果body是multipart/form-data,有没有简单的方法从日志中过滤掉二进制数据(文件内容)?
          【解决方案20】:

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

          长话短说......只需使用这个类来代替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 ? "\n\n" + 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 ? "\n\n" + 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();
              }
          }
          

          我同意这样做是很愚蠢的。

          【讨论】:

            【解决方案21】:

            正如@MilacH 指出的那样,实现中存在错误。如果返回 statusCode > 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=================================================");
                }
            
            }
            

            【讨论】:

              【解决方案22】:

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

              【讨论】:

                【解决方案23】:

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

                <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 的“最佳答案”看起来如此,因为使用 spring RestTemplate 时不一定会加载 httpclient 和所有 apache.http 库。

                【讨论】:

                • 发布的版本是什么?
                • 现在发布的版本是0.2
                • 易用性很好,但缺少标题
                • 另外:LoggingRequestInterceptor 中所有有用的方法都是私有的,这在扩展时是个问题(可以被保护)
                • 很遗憾,5 分钟后我无法编辑 cmets。要记录标头,您所要做的就是:LoggingRequestInterceptor:traceRequest 中的log("Headers: {}", request.headers)LoggingRequestInterceptor:logResponse 中的log("Headers: {}", response.headers)。您可能想考虑为记录标题和正文添加一些标志。此外 - 您可能需要检查正文内容类型以进行日志记录(例如仅记录应用程序/json*)。这也应该是可配置的。总而言之,通过这些小调整,您将拥有一个不错的库来传播。干得好:)
                【解决方案24】:

                也想添加我的实现。我为所有丢失的分号道歉,这是用 Groovy 编写的。

                我需要比提供的公认答案更可配置的东西。这是一个非常灵活的 REST 模板 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=================================================")
                        }
                    }
                }
                

                Rest 模板 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
                    }
                }
                

                【讨论】:

                  【解决方案25】:

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

                  Why my custom ClientHttpRequestInterceptor with empty response

                  【讨论】:

                    【解决方案26】:

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

                    【讨论】:

                      【解决方案27】:

                      我的记录器配置使用了 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) 发送的消息

                      【讨论】:

                      • 跟踪日志可能过于冗长......如果每秒有数千个请求怎么办??
                      猜你喜欢
                      • 2015-10-07
                      • 2022-06-21
                      • 1970-01-01
                      • 2014-06-13
                      • 2019-10-26
                      • 1970-01-01
                      • 1970-01-01
                      • 2023-02-02
                      相关资源
                      最近更新 更多