Spring Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
REST Top

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE

1. Overview

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:

<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 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.

Thus, we'll create a new LoggingInterceptor to customize our logs:

public class LoggingInterceptor implements ClientHttpRequestInterceptor {

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

    @Override
    public ClientHttpResponse intercept(HttpRequest req, byte[] reqBody, ClientHttpRequestExecution ex)
      throws IOException {
        log.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"));
        log.debug("Response body: {}", body);
        return response;
    }
}

As can be seen, we can much better read the useful information from the output:

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

5. Conclusion

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

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

Or, we can implement a custom interceptor to get more human-readable logs.

As always, the source code for this article is available over on GitHub.

Spring bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
REST bottom

I just announced the new Learn Spring course, focused on the fundamentals of Spring 5 and Spring Boot 2:

>> CHECK OUT THE COURSE
6 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Libor
Libor
2 months ago

Hello, there is a slight issue with the a custom response logging interceptor. The interceptor consumes the response inputstream, so there is nothing more to consume for actual response converter and therefore response.getBody() returns null, when the interceptor is used. One solution is to use BufferingClientHttpRequestFactory to save the inputstream in a byte array so it can be consumed multiple times. Unfortunately, by doing this, you loose streaming feature so you have to be careful about OOM error for large responses (like downloading large files). I solved this by subclassing BufferingClientHttpRequestFactory and turned off buffering selectively when I expected a… Read more »

Loredana Crusoveanu
2 months ago
Reply to  Libor

Hi Libor,
Thanks for the feedback.
Practically speaking, we’ll log the body of an API response only in development for debugging purposes, not in production. In other words, there would not be a lot of scenarios where you’ll consume the response again after logging it.
Having said that, this is a good point, and we’ll update the article to warn of it and add an alternative as you suggested.
Cheers.

SunilManaja
SunilManaja
2 months ago

Thank you.
Can you please share details how can we do the same for WebClient

Loredana Crusoveanu
2 months ago
Reply to  SunilManaja

Hi Sunil,

We have an article on the WebClient here: https://www.baeldung.com/spring-log-webclient-calls I’ll add a link to it in this article as well.

Cheers.

Chris Witte
Chris Witte
2 months ago

When using this LoggingInterceptor with RestTemplate, the logged response is correct. unfortunately, the interceptor consumes the inputStream, and the originating client does not get the response body.

Loredana Crusoveanu
2 months ago
Reply to  Chris Witte

Thanks for the note, Chris, we’ll update the article.

Comments are closed on this article!