Spring RestTemplate Request/Response Logging – Spring RestTemplate请求/响应记录

最后修改: 2020年 6月 23日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’re going to learn how to implement efficient RestTemplate request/response logging. This is especially useful to debug exchange between two servers.

在本教程中,我们将学习如何实现高效的RestTemplate请求/响应记录。这对于调试两个服务器之间的交换特别有用。

Unfortunately, Spring Boot doesn’t provide an easy way to inspect or log a simple JSON response body.

不幸的是,Spring Boot没有提供一个简单的方法来检查或记录一个简单的JSON响应体。

We’re going to explore several methods to log either HTTP headers or, which is the most interesting part, the HTTP body.

我们将探索几种方法来记录HTTP头或最有趣的部分,即HTTP正文。

Note: the Spring RestTemplate will be deprecated, to be replaced by the WebClient. You can find a similar article using WebClient here: Logging Spring WebClient Calls.

注意:Spring的RestTemplate将被弃用,由WebClient取代。你可以在这里找到一篇使用WebClient的类似文章。记录 Spring WebClient 的调用

2. Basic Logging With RestTemplate

2.使用RestTemplate的基本日志

Let’s start configuring the RestTemplate logger in the application.properties file:

让我们开始application.properties文件中配置RestTemplate记录器。

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

As a result, we can see only basic information like the request URL, method, body, and response status:

因此,我们只能看到基本信息,如请求URL、方法、正文和响应状态。

o.s.w.c.RestTemplate - HTTP POST http://localhost:8082/spring-rest/persons
o.s.w.c.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
o.s.w.c.RestTemplate - Writing [my request body] with org.springframework.http.converter.StringHttpMessageConverter
o.s.w.c.RestTemplate - Response 200 OK

However, the response body isn’t logged here, which is unfortunate because it’s the most interesting part.

然而,这里没有记录响应体,这很不幸,因为它是最有趣的部分。

To solve this, we’ll choose either Apache HttpClient or a Spring interceptor.

为了解决这个问题,我们将选择Apache HttpClient或Spring拦截器。

3. Logging Headers/Body With Apache HttpClient

3.用Apache HttpClient记录报头/正文

First, we have to make RestTemplate use the Apache HttpClient implementation.

首先,我们必须使RestTemplate使用Apache HttpClient>实现。

We’ll need the Maven dependency:

我们需要Maven的依赖性

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

When creating the RestTemplate instance, we should tell it we’re using Apache HttpClient:

在创建RestTemplate实例时,我们应该告诉它我们正在使用Apache HttpClient

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

Then, let’s configure the client logger in the application.properties file:

然后,让我们在application.properties文件中配置客户端记录器。

logging.level.org.apache.http=DEBUG
logging.level.httpclient.wire=DEBUG

Now we can see both request/response headers and body:

现在我们可以看到请求/响应头和正文。

    o.a.http.headers - http-outgoing-0 >> POST /spring-rest/persons HTTP/1.1
    o.a.http.headers - http-outgoing-0 >> Accept: text/plain, application/json, application/*+json, */*
// ... more request headers
    o.a.http.headers - http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.9 (Java/1.8.0_171)
    o.a.http.headers - http-outgoing-0 >> Accept-Encoding: gzip,deflate
org.apache.http.wire - http-outgoing-0 >> "POST /spring-rest/persons HTTP/1.1[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Accept: text/plain, application/json, application/*+json, */*[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Content-Type: text/plain;charset=ISO-8859-1[\r][\n]"
// ... more request headers
org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "my request body"
org.apache.http.wire - http-outgoing-0 << "HTTP/1.1 200 [\r][\n]"
org.apache.http.wire - http-outgoing-0 << "Content-Type: application/json[\r][\n]"
// ... more response headers
org.apache.http.wire - http-outgoing-0 << "Connection: keep-alive[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "21[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "["Lucie","Jackie","Danesh","Tao"][\r][\n]"

However, these logs are verbose and not handy to debug.

然而,这些日志是冗长的,不便于调试

We’ll see how to solve this in the following chapter.

我们将在下一章看到如何解决这个问题。

4. Logging Body With a RestTemplate Interceptor

4.用RestTemplate拦截器记录正文

As another solution, we can configure interceptors for RestTemplate.

作为另一个解决方案,我们可以配置RestTemplate的拦截器。

4.1. Logging Interceptor Implementation

4.1.记录拦截器的实现

First, let’s create a new LoggingInterceptor to customize our logs. This interceptor logs the request body as a simple byte array. However, for the response, we have to read the entire body stream:

首先,让我们创建一个新的LoggingInterceptor来定制我们的日志。这个拦截器将请求主体记录为一个简单的字节数组。然而,对于响应,我们必须要读取整个body流。

public class LoggingInterceptor implements ClientHttpRequestInterceptor {

    static Logger LOGGER = LoggerFactory.getLogger(LoggingInterceptor.class);

    @Override
    public ClientHttpResponse intercept(
      HttpRequest req, byte[] reqBody, ClientHttpRequestExecution ex) throws IOException {
        LOGGER.debug("Request body: {}", new String(reqBody, StandardCharsets.UTF_8));
        ClientHttpResponse response = ex.execute(req, reqBody);
        InputStreamReader isr = new InputStreamReader(
          response.getBody(), StandardCharsets.UTF_8);
        String body = new BufferedReader(isr).lines()
            .collect(Collectors.joining("\n"));
        LOGGER.debug("Response body: {}", body);
        return response;
    }
}

Beware, this interceptor has an impact on the response content itself, as we’ll discover in the next chapter.

注意,这个拦截器对响应内容本身有影响,我们将在下一章发现。

4.2. Using Interceptor With RestTemplate

4.2.在RestTemplate中使用拦截器

Now, we must deal with a streaming problem: As the interceptor consumes the response stream, our client application will see an empty response body.

现在,我们必须处理一个流问题:当拦截器消耗响应流时,我们的客户端应用程序将看到一个空的响应体

To avoid that, we should use BufferingClientHttpRequestFactory: it buffers stream content into memory. This way, it can be read twice: once by our interceptor, and a second time by our client application:

为了避免这种情况,我们应该使用BufferingClientHttpRequestFactory:它将流内容缓冲到内存中。这样,它可以被读取两次:一次由我们的拦截器读取,另一次由我们的客户端应用程序读取。

ClientHttpRequestFactory factory = 
        new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
        RestTemplate restTemplate = new RestTemplate(factory);

However, the use of this factory involves a performance drawback, which we’ll describe in the next subsection.

然而,使用这种工厂涉及到一个性能缺陷,我们将在下一小节中描述。

Then we can add our logging interceptor to the RestTemplate instance — we’ll append it after the existing interceptors, if any:

然后我们可以将我们的日志拦截器添加到RestTemplate实例中–我们将把它追加到现有的拦截器之后,如果有的话。

List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
    interceptors = new ArrayList<>();
}
interceptors.add(new LoggingInterceptor());
restTemplate.setInterceptors(interceptors);

As a result, only the necessary information is present in the logs:

因此,只有必要的信息才会出现在日志中。

c.b.r.l.LoggingInterceptor - Request body: my request body
c.b.r.l.LoggingInterceptor - Response body: ["Lucie","Jackie","Danesh","Tao"]

4.3. RestTemplate Interceptor Drawback

4.3.RestTemplate拦截器的缺点

A mentioned before, the use of BufferingClientHttpRequestFactory has a serious drawback: it undoes the benefits of streaming. As a consequence, loading the entire body data into memory could expose our application to performance issues. Worse, it can lead to OutOfMemoryError.

前面提到,使用BufferingClientHttpRequestFactory有一个严重的缺点:它破坏了流媒体的好处。因此,将整个主体数据加载到内存中可能会使我们的应用程序暴露在性能问题中。更糟糕的是,它可能导致OutOfMemoryError

To prevent this, one possible option is to assume that these verbose logs would be turned off when the data volume scales up, which typically happens in production. For example, we can use a buffered RestTemplate instance only if DEBUG level is enabled on our logger:

为了防止这种情况,一个可能的选择是假设当数据量扩大时,这些冗长的日志会被关闭,这通常发生在生产中。例如,我们可以只有在DEBUG级别在我们的记录器上被启用的情况下才使用缓冲的RestTemplate实例

RestTemplate restTemplate = null;
if (LOGGER.isDebugEnabled()) {
    ClientHttpRequestFactory factory 
    = new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
    restTemplate = new RestTemplate(factory);
} else {
    restTemplate = new RestTemplate();
}

Similarly, we’ll ensure that our interceptor only reads the response when DEBUG logging is enabled:

同样,我们将确保我们的拦截器只在DEBUG日志启用时读取响应

if (LOGGER.isDebugEnabled()) {
    InputStreamReader isr = new InputStreamReader(response.getBody(), StandardCharsets.UTF_8);
    String body = new BufferedReader(isr)
        .lines()
        .collect(Collectors.joining("\n"));
    LOGGER.debug("Response body: {}", body);
}

5. Conclusion

5.总结

RestTemplate request/response logging is not a straightforward matter, as Spring Boot doesn’t include it out-of-the-box.

RestTemplate请求/响应记录不是一个简单的问题,因为Spring Boot并不包含开箱即用的功能。

Fortunately, we’ve seen that we can use the Apache HttpClient logger to get a verbose trace of exchanged data.

幸运的是,我们已经看到,我们可以使用Apache HttpClient日志器来获得交换数据的粗略追踪

Or, we can implement a custom interceptor to get more human-readable logs. However, this can induce performance drawbacks for large data volumes.

或者,我们可以实现一个自定义拦截器来获得更多人类可读的日志。然而,对于大数据量来说,这可能会诱发性能上的缺陷。

As always, the source code for this article is available over on GitHub in the test folder. The example uses the RestTemplate in a live test for the REST endpoint defined in the same project.

一如既往,本文的源代码可在GitHub上test文件夹中找到。该示例在同一项目中定义的REST端点的实时测试中使用RestTemplate