Автор оригинала: Benjamin Caure.
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.
Unfortunately, Spring Boot doesn’t provide an easy way to inspect or log a simple JSON response body.
We’re going to explore several methods to log either HTTP headers or, which is the most interesting part, the HTTP body.
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 .
2. Basic Logging With RestTemplate
Let’s start configuring the RestTemplate logger in the application.properties file:
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:
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.
3. Logging Headers/Body With Apache HttpClient
First, we have to make RestTemplate use the Apache HttpClient implementation.
We’ll need the Maven dependency :
org.apache.httpcomponents httpclient 4.5.12
When creating the RestTemplate instance, we should tell it we’re using Apache HttpClient :
RestTemplate restTemplate = new RestTemplate(); restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
Then, let’s configure the client logger in the application.properties file:
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
As another solution, we can configure interceptors for RestTemplate .
4.1. Logging Interceptor Implementation
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:
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
Теперь мы должны иметь дело с проблемой потокового: Поскольку перехватчик потребляет поток откликов, наше клиентское приложение будет видеть пустой орган отклика .
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:
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:
Listinterceptors = restTemplate.getInterceptors(); if (CollectionUtils.isEmpty(interceptors)) { interceptors = new ArrayList<>(); } interceptors.add(new LoggingInterceptor()); restTemplate.setInterceptors(interceptors);
В результате в журналах присутствует только необходимая информация:
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
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 .
Чтобы предотвратить это, одним из возможных вариантов является предполагать, что эти многословные журналы будут выключены при масштабе объема данных, что обычно происходит в производстве. Например, мы можем использовать буферную РестТемплет например, только в том случае, ОТЛАДИТЬ уровень включен на нашем лесозаготовителя:
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 :
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. Заключение
РестТемплет регистрация запросов/ответов не является простым вопросом, так как Spring Boot не включает ее вне коробки.
К счастью, мы видели, что мы можем использовать регистратор Apache HttpClient, чтобы получить многословный обменивались данными.
Или мы можем реализовать пользовательский перехватчик, чтобы получить более читаемые человеком журналы . Однако это может привести к недостаткам производительности больших объемов данных.
Как всегда, исходный код для этой статьи доступен более на GitHub в тестовый папка. В этом примере используется РестТемплет в живом тесте для конечной точки REST, определенной в том же проекте.