Spring RestTemplate - how to enable full debugging/logging of requests/responses?
Asked Answered
S

30

273

I have been using the Spring RestTemplate for a while and I consistently hit a wall when I'am trying to debug it's requests and responses. I'm basically looking to see the same things as I see when I use curl with the "verbose" option turned on. For example :

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

Would display both the sent data and the received data (including the headers, cookies, etc.).

I have checked some related posts like : How do I log response in Spring RestTemplate? but I haven't managed to solve this issue.

One way to do this would be to actually change the RestTemplate source code and add some extra logging statements there, but I would find this approach really a last resort thing. There should be some way to tell Spring Web Client/RestTemplate to log everything in a much friendlier way.

My goal would be to be able to do this with code like :

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

and then to get the same type of debug information (as I get with curl) in the log file or in the console. I believe this would be extremely useful for anyone that uses the Spring RestTemplate and has problems. Using curl to debug your RestTemplate problems just doesn't work (in some cases).

Stroke answered 31/10, 2011 at 9:58 Comment(7)
Warning to anyone reading in 2018: There isn't a simple answer to this!Toughie
Most easy way is to use a breakpoint in write(...) method of AbstractHttpMessageConverter class, there is a outputMessage object where you could see the data. P.S. You can copy the value and then format it with online formatter.Ransell
seems like this should be easy to do in Spring, but, judging from the answers here - not the case. So one other solution, would be to bypass Spring entirely and use a tool like Fiddler to capture the request/response.Snowplow
read the answer to this question from the following a link : spring-resttemplate-how-to-enable-full-debugging-logging-of-requests-responsesWillner
July 2019: As there is still no simple solution to this question, I tried to give a summary of the other 24 answers (so far) and their comments and discussions in my own answer below. Hope it helps.Vicarage
Nothing seemed to work with my Intellij/gradle/Spring Boot config, but I managed to fix my issue with the online webhook.site logger.Routh
I think it's time to walk down the path of "restTemplate alternatives"Luwian
S
25

I finally found a way to do this in the right way. Most of the solution comes from How do I configure Spring and SLF4J so that I can get logging?

It seems there are two things that need to be done :

  1. Add the following line in log4j.properties : log4j.logger.httpclient.wire=DEBUG
  2. Make sure spring doesn't ignore your logging config

The second issue happens mostly to spring environments where slf4j is used (as it was my case). As such, when slf4j is used make sure that the following two things happen :

  1. There is no commons-logging library in your classpath : this can be done by adding the exclusion descriptors in your pom :

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    
  2. The log4j.properties file is stored somewhere in the classpath where spring can find/see it. If you have problems with this, a last resort solution would be to put the log4j.properties file in the default package (not a good practice but just to see that things work as you expect)

Stroke answered 1/11, 2011 at 8:41 Comment(4)
This doesn't work for me, I did both things. I don't understand why do I need to put log4j.properties when it's not used anyway in my project (checked by mvn dependency:tree)Operand
This doesn't work for me either. I even tried setting the root logger to Debug mode and still nothing.Whetstone
"httpclient.wire.content" and "httpclient.wire.header" are logger names from the Axis2 framework. They can be used to log e.g. SOAP requests in a Spring project if those are done using Axis2.Burress
httpclient.wire is actually from the Apache HttpComponents HttpClient library (see hc.apache.org/httpcomponents-client-ga/logging.html). This technique will only work if you have RestTemplate configured to use the HttpComponentsClientHttpRequestFactoryPtolemy
L
240

Just to complete the example with a full implementation of ClientHttpRequestInterceptor to trace request and response:

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

}

Then instantiate RestTemplate using a BufferingClientHttpRequestFactory and the LoggingRequestInterceptor:

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

The BufferingClientHttpRequestFactory is required as we want to use the response body both in the interceptor and for the initial calling code. The default implementation allows to read the response body only once.

Lafave answered 8/10, 2015 at 7:54 Comment(23)
Good answer! Though an issue is that if the response code is not a 200, traceResponse will throw.Geehan
This is wrong. If you read the stream, the application code will not be able to read the response.Whetstone
we have given the RestTemplate a BufferingClientHttpRequestFactory so we can read the response twice.Lafave
We have been using this technique for about 3 months now. It only works with RestTemplate configured with a BufferingClientHttpResponseWrapper as @sofienezaghdoudi implies. However, it does not work when used in tests using spring's mockServer framework since MockRestServiceServer.createServer(restTemplate) overwrites the RequestFactory to InterceptingClientHttpRequestFactory.Smoothie
The technique is good, implementation is wrong. 404 case, response.getBody() throw IOException -> you never get the log for out and even worst, it will become a ResourceAccessException in your further code, instead of a RestClientResponseExceptionVernita
Side note: as of the time of commenting this will not work for OAuth2RestTemplate. See Oauth2RestTemplate should propagate its requestFactory, messageConverters and interceptors to its accessTokenProviderMarlysmarmaduke
thanks for the reply. But this is bad practice to have multiple "log.debug" as it could be spread over a lot of other logs. It's better to use a single log.debug instruction so you're sure everything is on the same placeMonotint
How to use ClientHttpRequestInterceptor without RestTemplate? Using simple @RestControllerMerimerida
Nice solution, except it crashes on 5xx, which might be undesirable. You can use StreamUtils.copyToString to copy response body and then print it out, instead of bufferedReader: String bodyText = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8); log.debug("Response body: {}", bodyText);Norenenorfleet
@Marlysmarmaduke This method doesn't work on Oauth2RestTemplate. As an alternative you can change the requestFactory according to this answer #16574001Spirillum
new RestTemplate(new BufferingClientHttpRequestFactory(new HttpComponentsClientHttpRequestFactory())) works better to me as long as is logs non-success responses as well. Note, that you need to add org.apache.httpcomponents:httpclient into dependenciesMagdeburg
As @Vernita already mentioned, the implementation leads to that the errorHandler is not invoked.Choreographer
Kind of amusing to see, with all the suggestions and comments here, that someone appears to have copied the above code lock, stock and barrel and passed it off without attribution. Oh, wait, they changed the C style logging from 'info' to 'debug' - call it good! #sad objectpartners.com/2018/03/01/…Snowplow
When I added new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()) to restTemplate I got new problem related #25163631 . Then I configured new HttpClient that added new fails in my integration test, because some behaviour related to cookies where changed. Actually, the easiest solution is to reuse(copy/paste) org.springframework.http.client.BufferingClientHttpResponseWrapper instead of configuring new requestFactoryOmora
How do I write a Unit Test for this class?Delacruz
For people using RestTemplateBuilder after building better use restTemplate.getInterceptors().add(new LoggingRequestInterceptor()); just to avoid loosing the other interceptors, like auth, ... .Anemia
for performance and memory consumption reasons, it would be better avoiding reading body lines into memory when debug log level is not enabledGaskins
@Magdeburg your fix works for me! :) I saw the blog post and I was very annoyed : copy pasting without attribution + no update about this bug, what a waste of time !Irisirisa
If I am using RestTemplateBuilder instead, how should I approach the instantiating of the requestFactory?Tudor
@Snowplow an others raising this issue of plagiarism. The answerer posted this in 2015. The article that was linked was published in 2018. Also, if you pick a snippet of this code and google it, you will find more than a handful of hits. As much of a problem as answers without attribution is, also is the problem of SEO people ripping answers from SO and turning into a blog post also without attribution.Dottiedottle
How can this be a solution? it can easily lead to potential memory leaks (because of BufferingClientHttpRequestFactory). Also, can easily exhaust the memory.Fountainhead
If we read it in the interceptor using stream, the body won’t be available for RestTemplate to deserialize it into object modelExtension
It's giving IOException for PATCH http methods at ClientHttpResponse response = execution.execute(request, body);Gluttonize
X
177

in Spring Boot you can get the full request/response by setting this in properties (or other 12 factor method)

logging.level.org.apache.http=DEBUG

this outputs

-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":"[email protected]","new":true}"

and response

-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":"[email protected]","new":true}"

or just logging.level.org.apache.http.wire=DEBUG which seems to contain all of the relevant information

Xylograph answered 23/8, 2016 at 19:48 Comment(9)
This was the simplest thing that did what I wanted. I highly encourage including this in the accepted answer.Vanguard
According to the javadoc of RestTemplate : 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 HttpComponentsStagey
RestTemplate doesn't use these Apache classes be default, as pointed out by @OrtomalaLokni, so you should also include how to use them in addition to how to print the debug when they are being used.Mccain
I am getting like this : http-outgoing-0 << "[0x1f][0x8b][0x8][0x0][0x0][0x0][0x0][0x0]Patroon
@ParthaSarathiGhosh The content is probably gzip encoded which is why you're not seeing the raw text.Omarr
This simple solution works if your app is configured to use ApacheJanessajanet
How should I use this if I using spring 4Salomie
In my case, each field of the request seems to be printing in a separate line... not sure whyGlaciology
Is there a way log only the request body and not the response?Bathometer
A
84

Extending @hstoerr answer with some code:


Create LoggingRequestInterceptor to log requests responses

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

Setup 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());
Anticosti answered 24/3, 2014 at 20:41 Comment(9)
This is not available until spring-3.1 version.Hoptoad
it not answer the question of 'logging response', but leave a //do logging comment instead.Duo
doing the logging was easy, but this works only for requests, I don't see response bodies, suppose I have response object, but reading it's stream is not good idea.Operand
@PavelNiedoba Why would you say that reading its stream is not a good idea?Morpho
By the way, I was able to manage logging both request and response via this approach - requests were a bit tricky as they have the outputstream, which you have to parse as bytearrayoutputstream. I am using springTemplate.exchange method, because I need to send some authentication headers as well.Morpho
Reading stream of response is not good because if you do it you actually consume the data and original receiver will not receive it. I solved it by employing king of stream splitter added to the filter chainOperand
@PavelNiedoba The BufferClientHttpRequestFactory allows the response to be read more than one time.Anticosti
This works well if you need to store info about request/response in a database for debugging and regular logging doesn't suit your needs.Mikiso
The crux of this problem is how to log the response string. This answer conveniently skips over that detail.Whetstone
K
51

Your best bet is to add logging.level.org.springframework.web.client.RestTemplate=DEBUG to the application.properties file.

Other solutions like setting log4j.logger.httpclient.wire will not always work because they assume you use log4j and Apache HttpClient, which is not always true.

Note, however, that this syntax will work only on latest versions of Spring Boot.

Kilbride answered 4/7, 2016 at 13:15 Comment(6)
This is not logging the request and response body, just the url and request type (spring-web-4.2.6)Ribeiro
You are right, it is not a wire logging, it only includes essential information like url, resepone code, POST parameters etc.Kilbride
what you really want is this https://mcmap.net/q/108300/-spring-resttemplate-how-to-enable-full-debugging-logging-of-requests-responsesXylograph
This is fine but response body could not be seen!Yeti
Brilliant. Although it doesn't print the response body, it's still very useful. Thank You.Vicarage
will it print headers as wellTransudate
E
41

You can use spring-rest-template-logger to log RestTemplate HTTP traffic.

Add a dependency to your Maven project:

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

Then customize your RestTemplate as follows:

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

Ensure that debug logging is enabled in application.properties:

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

Now all RestTemplate HTTP traffic will be logged to org.hobsoft.spring.resttemplatelogger.LoggingCustomizer at debug level.

DISCLAIMER: I wrote this library.

Endblown answered 1/11, 2017 at 10:16 Comment(7)
Glad it helped @RaffaelBecharaRameh. It was initially downvoted because I didn't embed instructions from the linked project. Feel free to upvote if you found it useful!Endblown
Do you support via Gradle?Comitia
@Comitia spring-rest-template-logger is a regular Maven artifact, so it should work fine with Gradle.Endblown
For Spring Boot 2, you need to add logging.level.org.hobsoft.spring.resttemplatelogger.LoggingCustomizer=DEBUG to application.properties to view the log.Delve
Thanks @Delve - I've updated the answer and project README.Endblown
Hi Mark, thanks for developing this library, I would like to learn how did you handle inputstream ? did you use BufferingClientHttpRequestFactory ?Macadam
Hi @erhanasikoglu, you're welcome! That's right, you can see it in use here: github.com/markhobson/spring-rest-template-logger/blob/master/…Endblown
S
33

The solution given by xenoterracide to use

logging.level.org.apache.http=DEBUG

is good but the problem is that by default Apache HttpComponents is not used.

To use Apache HttpComponents add to your pom.xml

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

and configure RestTemplate with :

RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory());
Stagey answered 1/2, 2017 at 15:38 Comment(2)
Easiest way, I'll only add that it doesn't work with MockRestServiceServer, as it overwrites requestFactory.Spartacus
Working well and no issues less config !Yeti
W
32

None of these answers actually solve 100% of the problem. mjj1409 gets most of it, but conveniently avoids the issue of logging the response, which takes a bit more work. Paul Sabou provides a solution that seems realistic, but doesn't provide enough details to actually implement (and it didn't work at all for me). Sofiene got the logging but with a critical problem: the response is no longer readable because the input stream has already been consumed!

I recommend using a BufferingClientHttpResponseWrapper to wrap the response object to allow reading the response body multiple times:

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

}

This will not consume the InputStream because the response body is loaded into memory and can be read multiple times. If you do not have the BufferingClientHttpResponseWrapper on your classpath, you can find the simple implementation here:

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

For setting up the RestTemplate:

LoggingRequestInterceptor loggingInterceptor = new LoggingRequestInterceptor();
restTemplate.getInterceptors().add(loggingInterceptor);
Whetstone answered 1/3, 2016 at 21:7 Comment(6)
same, responseCopy.getBody() throw IOexception in case of 404, so you never send back to your further code the response and the normally RestClientResponseException become a ResourceAccessExceptionVernita
You should check status==200, before responseCopy.getBody()Seumas
But it's package-private. Did you put your LoggingRequestInterceptor in package 'org.springframework.http.client'?Spartacus
what about asyncRestTemplate? It would require to return a ListenableFuture when you intercept it which is not possible to alter with BufferingClientHttpResponseWrapper in a callback.Grani
@ÖmerFarukAlmalı In that case you will need to use chain or transform depending on the version of Guava you're using. See: #8192391Whetstone
Note that this library also uses a buffer to keep the response in memory, causing potential memory issues with large responses.Pneumatics
P
26

Logging RestTemplate

Option 1. Open debug logging.

Configurate RestTemplate

  • 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

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

Configurate logging

  • application.yml

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

Option 2. Using Interceptor

Wrapper Response

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

Implement Interceptor

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

}

Configurate RestTemplate

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

Configurate logging

  • Check the package of LoggingRestTemplate, for example in application.yml:

    logging: level: com.example.logging: DEBUG

Option 3. Using httpcomponent

Import httpcomponent dependency

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

Configurate RestTemplate

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

Configurate logging

  • Check the package of LoggingRestTemplate, for example in application.yml:

    logging: level: org.apache.http: DEBUG

Profiterole answered 31/10, 2011 at 9:59 Comment(2)
Just note: if you'd like to configure TestRestTemplate, configure RestTemplateBuilder: @Bean public RestTemplateBuilder restTemplateBuilder() { return new RestTemplateBuilder().additionalInterceptors(Collections.singletonList(new LoggingRestTemplate())); }Monometallism
Note also that new InputStreamReader(responseWrapper.getBody(), StandardCharsets.UTF_8)); can throw an error if the "other end" returned an error. You may want to place it into a try block.Bucentaur
S
25

I finally found a way to do this in the right way. Most of the solution comes from How do I configure Spring and SLF4J so that I can get logging?

It seems there are two things that need to be done :

  1. Add the following line in log4j.properties : log4j.logger.httpclient.wire=DEBUG
  2. Make sure spring doesn't ignore your logging config

The second issue happens mostly to spring environments where slf4j is used (as it was my case). As such, when slf4j is used make sure that the following two things happen :

  1. There is no commons-logging library in your classpath : this can be done by adding the exclusion descriptors in your pom :

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    
  2. The log4j.properties file is stored somewhere in the classpath where spring can find/see it. If you have problems with this, a last resort solution would be to put the log4j.properties file in the default package (not a good practice but just to see that things work as you expect)

Stroke answered 1/11, 2011 at 8:41 Comment(4)
This doesn't work for me, I did both things. I don't understand why do I need to put log4j.properties when it's not used anyway in my project (checked by mvn dependency:tree)Operand
This doesn't work for me either. I even tried setting the root logger to Debug mode and still nothing.Whetstone
"httpclient.wire.content" and "httpclient.wire.header" are logger names from the Axis2 framework. They can be used to log e.g. SOAP requests in a Spring project if those are done using Axis2.Burress
httpclient.wire is actually from the Apache HttpComponents HttpClient library (see hc.apache.org/httpcomponents-client-ga/logging.html). This technique will only work if you have RestTemplate configured to use the HttpComponentsClientHttpRequestFactoryPtolemy
V
24

---- July 2019 ----

(using Spring Boot)

I was surprised that Spring Boot, with all it's Zero Configuration magic, doesn't provide an easy way to inspect or log a simple JSON response body with RestTemplate. I looked through the various answers and comments provided here, and am sharing my own distilled version of what (still) works and seems to me like a reasonable solution, given the current options (I'm using Spring Boot 2.1.6 with Gradle 4.4)

1. Using Fiddler as http proxy

This is actually quite an elegant solution, as it bypasses all the cumbersome efforts of creating your own interceptor or changing the underlying http client to apache (see below).

Install and run Fiddler

and then

add -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888 to your VM Options

2. Using Apache HttpClient

Add Apache HttpClient to your Maven or Gradle dependencies.

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

Use HttpComponentsClientHttpRequestFactory as RequestFactory for RestTemplate. The simplest way to do that would be:

RestTemplate restTemplate = new RestTemplate();

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Enable DEBUG in your application.properties file (if you're using Spring Boot)

logging.level.org.apache.http=DEBUG

If you're using Spring Boot, you'll need to make sure you have a logging framework set up, e.g. by using a spring-boot-starter dependency that includes spring-boot-starter-logging.

3. Use an Interceptor

I'll let you read through the proposals, counter-proposals, and gotchas in the other answers and comments and decide for yourself if you want to go down that path.

4. Log URL and Response Status without Body

Although this doesn't meet the stated requirements of logging the body, it's a quick and simple way to start logging your REST calls. It displays the full URL and response status.

Simply add the following line to your application.properties file (assuming you're using Spring Boot, and assuming you are using a spring boot starter dependency that includes spring-boot-starter-logging)

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

The output will look something like this:

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]
Vicarage answered 29/7, 2019 at 11:0 Comment(3)
No. 4 is the easiest way to debug.Theatre
No. 2 worked for me. It logs body of request. Thank you!Acosmism
I found No. 3 an easy way to do this when I came to this issue.Matthiew
A
21

As stated in the other responses, the response body needs special treatment so it can be read repeatedly (by default, its contents get consumed on the first read).

Instead of using the BufferingClientHttpRequestFactory when setting up the request, the interceptor itself can wrap the response and make sure the content is retained and can be repeatedly read (by the logger as well as by the consumer of the response):

My interceptor, which

  • buffers the response body using a wrapper
  • logs in a more compact way
  • logs the status code identifier as well (e.g. 201 Created)
  • includes a request sequence number allowing to easily distinguish concurrent log entries from multiple threads

Code:

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

Configuration:

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

Example log output:

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", ...  }
Alie answered 8/10, 2018 at 9:1 Comment(4)
This one works with the 2019 Spring version keeping the body intact.Accomplished
Works on Spring 2.1.10 :) ThanksOverthrust
I was struggling when trying this solution, I thought it wasn't working, then I figured out that the logs were added as Debug, so after I set my log level to DEBUG, the logs showed up. I know it's kinda obvious (my bad I didn't notice that), but I think it's worth to include this info on your solution, which is pretty good, BTW. For me, this info would have saved some investigation time lol. After that, I could find out what I was doing wrong in my request body. Excellent approach! Thanks. :)Pelecypod
BufferedClientHttpResponse causes a memory leak because you have a reference to response objetMarisolmarissa
I
13

application.properties

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

application.yml

logging:
  level:  
    root: WARN
    org.springframework.web.client: DEBUG
Interrelation answered 10/1, 2019 at 20:31 Comment(0)
E
12

Besides the HttpClient logging described in the other answer, you can also introduce a ClientHttpRequestInterceptor that reads the body of the request and the response and logs it. You might want to do this if other stuff also uses the HttpClient, or if you want a custom logging format. Caution: you will want to give the RestTemplate a BufferingClientHttpRequestFactory such that you can read the response twice.

Excitant answered 25/2, 2014 at 14:42 Comment(0)
A
10

This might not be the correct way to do it, but I think this is the most simple approach to print requests and responses without filling too much in logs.

By adding below 2 lines application.properties logs all requests and responses 1st line in order to log the requests and 2nd line to log the responses.

logging.level.org.springframework.web.client.RestTemplate=DEBUG
logging.level.org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor=DEBUG
Addressograph answered 29/5, 2018 at 15:37 Comment(2)
Logging responses does not work for me. It just Logs Statuscode. Should it log payload?Kutenai
The class HttpEntityMethodProcessor (v5.1.8) doesn't log anything.Vicarage
L
6

Assuming RestTemplate is configured to use HttpClient 4.x, you can read up on HttpClient's logging documentation here. The loggers are different than those specified in the other answers.

The logging configuration for HttpClient 3.x is available here.

Lamothe answered 13/11, 2014 at 15:13 Comment(0)
C
6

So many responses here require coding changes and customized classes and it really is not necessary. Gte a debugging proxy such as fiddler and set your java environment to use the proxy on the command line (-Dhttp.proxyHost and -Dhttp.proxyPort) then run fiddler and you can see the requests and responses in their entirety. Also comes with many ancillary advantages such as the ability to tinker with the results and responses before and after they are sent to run experiments before committing to modification of the server.

Last bit of an issue that can come up is if you must use HTTPS, you will need to export the SSL cert from fiddler and import it into the java keystore (cacerts) hint: default java keystore password is usually "changeit".

Charged answered 8/10, 2018 at 19:12 Comment(3)
This worked for me using intellij and the regular install of fiddle. I edited the Run Configuration and set VM options to -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888.Avicenna
Thanks! This is a pretty elegant solution compared to writing your own Interceptor.Vicarage
Yes, this is the direction we went and haven't gone back to messing w/ Spring / Interceptors / logging and so on. Though not Fiddler - we've been using the TCP/IP Monitor eclipse plugin, likely there is something similar in IntelliJ. For me Fiddler is great, but causes various issues with certificates, VPNs, so, depending on your environment, it might not be the ideal solution.Snowplow
G
5

For logging to Logback with help from Apache HttpClient:

You need Apache HttpClient in classpath:

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

Configure your RestTemplate to use HttpClient:

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

To log requests and responses, add to Logback configuration file:

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

Or to log even more:

<logger name="org.apache.http" level="DEBUG"/>
Glomerate answered 1/10, 2019 at 14:57 Comment(4)
What logback configuration file?Possibly
@Possibly logback.xml or logback-test.xml for tests.Glomerate
It also works with org.apache.http.wire=DEBUG in your application.properties nowPossibly
@Possibly if you are using Spring-Boot. My answer works without Boot.Glomerate
C
2

Adding to above discussion this only represents Happy scenarios. probably you will not be able to log the response if an Error comes .

In this case plus all the cases above you must override DefaultResponseErrorHandler and set it like below

restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());
Cherriecherrita answered 7/4, 2016 at 13:43 Comment(0)
G
2

Strangely, none of these solutions work as RestTemplate does not seem to return the response on some client and server 500x errors. In which case, you will have log those as well by implementing ResponseErrorHandler as follows. Here is a draft code, but you get the point:

You can set the same interceptor as the error handler:

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

And the intercept implements both interfaces:

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);
    }
}
Gromyko answered 18/10, 2017 at 2:11 Comment(1)
What if body is multipart/form-data, is there an easy way to filter out binary data (file content) from the log ?Filature
E
2

Most of the above solution doesn't work properly when RestTemplate is used and there is a 4xx or 5xx response type as ClientHttpResponse's body is empty. Here is the solution that I used to log the whole HTTP request/response in RestTemplate without losing the response body information in all cases. The Spring boot version is <version>2.7.5</version>

1.Create LoggingInterceptor class

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.Attach it to the RestTemplate bean

@Configuration
public class RestTemplateConfig {

    @Bean
    public RestTemplate createRestTemplate(LoggingInterceptor loggingInterceptor) {

        RestTemplate restTemplate = new RestTemplate();
        restTemplate.setInterceptors(Collections.singletonList(loggingInterceptor));

        return restTemplate;
    }
}

3.Apply the appropriate level in the application

logging:
  level:
    com:
      test: DEBUG
Extension answered 7/2, 2023 at 8:33 Comment(0)
H
1

The trick of configuring your RestTemplate with a BufferingClientHttpRequestFactory doesn't work if you are using any ClientHttpRequestInterceptor, which you will if you are trying to log via interceptors. This is due to the way that InterceptingHttpAccessor (which RestTemplate subclasses) works.

Long story short... just use this class in place of RestTemplate (note this uses the SLF4J logging API, edit as needed):

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

I agree it's silly that it takes this much work just to do this.

Hoop answered 5/4, 2016 at 22:29 Comment(0)
C
1

As @MilacH pointed out, there is an error in the implementation. If an statusCode > 400 is returned a IOException is thrown, as the errorHandler is not invoked, from interceptors. The exception can be ignored and is then caught again in the handler method.

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

}
Choreographer answered 14/9, 2018 at 11:3 Comment(0)
D
1

org.apache.http.wire gives too unreadable logs, so I use logbook to log application Servlet and RestTemplate requests & responses with payloads.

build.gradle:

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

or Maven dependency:

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

application.properties (or trough 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);
        }
    }
}
Demarcation answered 29/1, 2020 at 11:0 Comment(0)
C
1

An easy way to solve the problem:

  1. Create a Bean of RestTemplate using RestTemplateBuilder: It will give you more control over connection time and reading time.
@Configuration
public class RestTemplateConfig {
  @Bean
  public RestTemplate restTemplate(RestTemplateBuilder builder) {
    return builder
      .setConnectTimeout(Duration.ofMillis(60000))
      .setReadTimeout(Duration.ofMillis(60000))
      .build();
  }

}
  1. Add this line to the resources/application.properties file: logging.level.org.springframework.web.client.RestTemplate=DEBUG
    Hope the problem will be resolved!
Capo answered 14/1, 2022 at 9:5 Comment(0)
C
0

Best solution now, just add dependency :

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

It contains a LoggingRequestInterceptor class you can add that way to your RestTemplate:

integrate this utility by adding it as an interceptor to a spring RestTemplate, in the following manner:

restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build());

and add an slf4j implementation to your framework like log4j.

or directly use "Zg2proRestTemplate". The "best answer" by @PaulSabou looks so so, since httpclient and all apache.http libs are not necessarily loaded when using a spring RestTemplate.

Copenhaver answered 22/8, 2017 at 8:35 Comment(5)
what is the released version?Maitilde
released version now is 0.2Copenhaver
ease of use is great, but it lacks headersRefutative
additionally: all useful methods in LoggingRequestInterceptor are private, which is a problem when it comes to extension (could be protected)Refutative
sadly, I can't edit comments after 5 minutes. All you have to do to log headers is this: log("Headers: {}", request.headers) in LoggingRequestInterceptor:traceRequest and log("Headers: {}", response.headers) in LoggingRequestInterceptor:logResponse. You might want to think about adding some flags for logging headers and body. Also - you may want to check body content type for logging (for example log only application/json*). This should be also configurable. all in all, with those little tweaks you'll have a nice library to spread. good work :)Refutative
R
0

Wanted to add my implementation of this as well. I apologize for all the missing semi-colons, this is written in Groovy.

I needed something more configurable than the accepted answer provided. Here's a rest template bean that's very agile and will log everything like the OP is looking for.

Custom Logging Interceptor Class:

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 Template Bean Definition:

@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
}

Implementation:

@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
    }
}
Rafaelrafaela answered 20/4, 2018 at 17:55 Comment(0)
S
0

I went through all answers, In case you need to set authentication type or connection time out, then you can do like this :

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;
Santosantonica answered 16/7, 2022 at 3:43 Comment(0)
R
0

From spring boot 3, the apache dependency is more recent and it is: <artifactId>httpclient5</artifactId> which is used

You must then use the following property:org.apache.hc.client5.http:DEBUG.

Renown answered 15/11, 2023 at 11:37 Comment(0)
H
-1

Related to the response using ClientHttpInterceptor, I found a way of keeping the whole response without Buffering factories. Just store the response body input stream inside byte array using some utils method that will copy that array from body, but important, surround this method with try catch because it will break if response is empty (that is the cause of Resource Access Exception) and in catch just create empty byte array, and than just create anonymous inner class of ClientHttpResponse using that array and other parameters from the original response. Than you can return that new ClientHttpResponse object to the rest template execution chain and you can log response using body byte array that is previously stored. That way you will avoid consuming InputStream in the actual response and you can use Rest Template response as it is. Note, this may be dangerous if your's response is too big

Hangdog answered 2/3, 2018 at 4:32 Comment(0)
S
-3

my logger config used xml

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

then you will get something like below:

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]

through HttpMessageConverterExtractor.java:92,you need continue to debug,and in my case,i got this:

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

and this:

outputMessage.getBody().flush();

outputMessage.getBody() contains the message http(post type) sends

Syne answered 19/1, 2017 at 2:36 Comment(1)
trace logging might be too much verbose... what if there's thousands of requests per second??Ymir

© 2022 - 2024 — McMap. All rights reserved.