Spring RestTemplate - come abilitare il debug / registrazione completo di richieste / risposte?


221

Ho usato Spring RestTemplate per un po 'e ho costantemente colpito un muro quando sto cercando di eseguire il debug delle sue richieste e risposte. Fondamentalmente sto cercando di vedere le stesse cose che vedo quando uso curl con l'opzione "verbose" attivata. Per esempio :

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

Visualizzerebbe sia i dati inviati che quelli ricevuti (inclusi intestazioni, cookie, ecc.).

Ho controllato alcuni post correlati come: Come posso registrare la risposta in Spring RestTemplate? ma non sono riuscito a risolvere questo problema.

Un modo per farlo sarebbe quello di cambiare effettivamente il codice sorgente RestTemplate e aggiungere alcune dichiarazioni di registrazione extra lì, ma troverei questo approccio davvero l'ultima risorsa. Dovrebbe esserci un modo per dire a Spring Web Client / RestTemplate di registrare tutto in un modo molto più amichevole.

Il mio obiettivo sarebbe quello di poterlo fare con codice come:

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

e quindi per ottenere lo stesso tipo di informazioni di debug (come ottengo con l'arricciatura) nel file di registro o nella console. Credo che questo sarebbe estremamente utile per chiunque usi Spring RestTemplate e abbia problemi. L'uso di curl per il debug dei problemi RestTemplate non funziona (in alcuni casi).


31
Avviso a chiunque legga nel 2018: non c'è una risposta semplice a questa!
davidfrancis,

3
Il modo più semplice è usare un punto di interruzione nel metodo write (...) della classe AbstractHttpMessageConverter, c'è un oggetto outputMessage dove puoi vedere i dati. PS È possibile copiare il valore e quindi formattarlo con il formatter online.
Sergey Chepurnov,

1
sembra che questo dovrebbe essere facile da fare in primavera, ma, a giudicare dalle risposte qui - non è il caso. Quindi un'altra soluzione sarebbe quella di bypassare completamente Spring e utilizzare uno strumento come Fiddler per acquisire la richiesta / risposta.
michaelok,


Luglio 2019: poiché non esiste ancora una soluzione semplice a questa domanda, ho cercato di fornire un riepilogo delle altre 24 risposte (finora) e dei loro commenti e discussioni nella mia risposta di seguito . Spero che sia d'aiuto.
Chris,

Risposte:


208

Solo per completare l'esempio con un'implementazione completa di ClientHttpRequestInterceptorper tracciare la richiesta e la risposta:

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

}

Quindi istanza RestTemplateusando a BufferingClientHttpRequestFactorye il LoggingRequestInterceptor:

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

Il BufferingClientHttpRequestFactoryè richiesto come vogliamo usare il corpo della risposta sia nella intercettore e per il codice chiamante iniziale. L'implementazione predefinita consente di leggere il corpo della risposta solo una volta.


28
Questo è sbagliato. Se leggi lo stream, il codice dell'applicazione non sarà in grado di leggere la risposta.
James Watkins,

28
abbiamo assegnato a RestTemplate un BufferingClientHttpRequestFactory in modo da poter leggere la risposta due volte.
sofiene zaghdoudi,

16
Usiamo questa tecnica da circa 3 mesi. Funziona solo con RestTemplate configurato con aBufferingClientHttpResponseWrapper come implica @sofienezaghdoudi. Tuttavia, non funziona se utilizzato nei test utilizzando il framework mockServer di spring poiché MockRestServiceServer.createServer(restTemplate)sovrascrive RequestFactory in InterceptingClientHttpRequestFactory.
RubesMN,

8
La tecnica è buona, l'implementazione è sbagliata. 404 case, response.getBody () lanciare IOException -> non otterrai mai il log per la fine e, peggio ancora, diventerà una ResourceAccessException nel tuo ulteriore codice, invece di RestClientResponseException
MilacH,

6
Grazie per la risposta. Ma è una cattiva pratica avere più "log.debug" in quanto potrebbe essere distribuito su molti altri registri. È meglio usare una singola istruzione log.debug in modo da essere sicuri che tutto sia nello stesso posto
user2447161

130

in Spring Boot puoi ottenere la richiesta / risposta completa impostandola in proprietà (o altro metodo a 12 fattori)

logging.level.org.apache.http=DEBUG

questo produce

-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connecting to localhost/127.0.0.1:41827
-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connection established 127.0.0.1:39546<->127.0.0.1:41827
-DEBUG o.a.http.impl.execchain.MainClientExec   : Executing request POST /v0/users HTTP/1.1
-DEBUG o.a.http.impl.execchain.MainClientExec   : Target auth state: UNCHALLENGED
-DEBUG o.a.http.impl.execchain.MainClientExec   : Proxy auth state: UNCHALLENGED
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> POST /v0/users HTTP/1.1
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Type: application/json;charset=UTF-8
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Length: 56
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Host: localhost:41827
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Connection: Keep-Alive
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Accept-Encoding: gzip,deflate
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "POST /v0/users HTTP/1.1[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Type: application/json;charset=UTF-8[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Length: 56[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Host: localhost:41827[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "{"id":null,"email":"xenoterracide@gmail.com","new":true}"

e risposta

-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connecting to localhost/127.0.0.1:41827
-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connection established 127.0.0.1:39546<->127.0.0.1:41827
-DEBUG o.a.http.impl.execchain.MainClientExec   : Executing request POST /v0/users HTTP/1.1
-DEBUG o.a.http.impl.execchain.MainClientExec   : Target auth state: UNCHALLENGED
-DEBUG o.a.http.impl.execchain.MainClientExec   : Proxy auth state: UNCHALLENGED
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> POST /v0/users HTTP/1.1
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Type: application/json;charset=UTF-8
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Length: 56
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Host: localhost:41827
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Connection: Keep-Alive
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Accept-Encoding: gzip,deflate
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "POST /v0/users HTTP/1.1[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Type: application/json;charset=UTF-8[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Length: 56[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Host: localhost:41827[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "{"id":null,"email":"xenoterracide@gmail.com","new":true}"

o semplicemente logging.level.org.apache.http.wire=DEBUGche sembra contenere tutte le informazioni pertinenti


4
Questa è stata la cosa più semplice che ha fatto quello che volevo. Incoraggio vivamente di includerlo nella risposta accettata.
michaelavila,

22
Secondo il javadoc di 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
Ortomala Lokni

22
RestTemplate non utilizza queste classi Apache come impostazione predefinita, come sottolineato da @OrtomalaLokni, quindi dovresti anche includere come usarle oltre a come stampare il debug quando vengono utilizzate.
Captain Man

Sto diventando così:http-outgoing-0 << "[0x1f][0x8b][0x8][0x0][0x0][0x0][0x0][0x0]
Partha Sarathi Ghosh,

2
@ParthaSarathiGhosh Il contenuto è probabilmente codificato in gzip ed è per questo che non vedi il testo non elaborato.
Matthew Buckett,

79

Estendere la risposta @hstoerr con un po 'di codice:


Crea LoggingRequestInterceptor per registrare le richieste di risposta

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

Questo non è disponibile fino alla versione primavera-3.1.
Gyan,

3
non risponde alla domanda di "risposta di registrazione", ma lascia invece un // commento di registrazione.
Jiang YD,

1
fare la registrazione è stato facile, ma funziona solo per le richieste, non vedo i corpi di risposta, supponiamo di avere un oggetto di risposta, ma leggere il suo flusso non è una buona idea.
Pavel Niedoba,

11
@PavelNiedoba BufferClientHttpRequestFactory consente di leggere la risposta più di una volta.
mjj1409,

2
Funziona bene se è necessario archiviare informazioni su richiesta / risposta in un database per il debug e la registrazione regolare non soddisfa le proprie esigenze.
GameSalutes

32

La tua scommessa migliore è quella di aggiungere logging.level.org.springframework.web.client.RestTemplate=DEBUGalapplication.properties file.

Altre soluzioni come l'impostazione log4j.logger.httpclient.wirenon funzioneranno sempre perché presumono che tu usi log4je ApacheHttpClient , il che non è sempre vero.

Si noti, tuttavia, che questa sintassi funzionerà solo sulle ultime versioni di Spring Boot.


5
Questo non sta registrando il corpo della richiesta e la risposta, solo l'URL e la richiesta di tipo (primavera-web-4.2.6)
DVE

1
Hai ragione, non è una wireregistrazione, include solo informazioni essenziali come url, codice resepone, parametri POST ecc.
gamliela,

1
ciò che si vuole veramente è questo stackoverflow.com/a/39109538/206466
xenoterracide

Questo va bene ma non è stato possibile vedere il corpo della risposta!
sunleo,

Brillante. Anche se non stampa il corpo della risposta, è comunque molto utile. Grazie.
Chris,

30

Nessuna di queste risposte risolve effettivamente il 100% del problema. mjj1409 ottiene la maggior parte, ma evita convenientemente il problema della registrazione della risposta, che richiede un po 'più di lavoro. Paul Sabou fornisce una soluzione che sembra realistica, ma non fornisce dettagli sufficienti per implementare effettivamente (e non ha funzionato affatto per me). Sofiene ha ottenuto la registrazione ma con un problema critico: la risposta non è più leggibile perché il flusso di input è già stato consumato!

Consiglio di utilizzare un BufferingClientHttpResponseWrapper per avvolgere l'oggetto response in modo da consentire la lettura del corpo della risposta più volte:

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

}

Ciò non consumerà InputStream perché il corpo della risposta è caricato in memoria e può essere letto più volte. Se non hai BufferingClientHttpResponseWrapper sul tuo percorso di classe, puoi trovare l'implementazione semplice qui:

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

Per impostare RestTemplate:

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

stesso, responseCopy.getBody () genera IOexception in caso di 404, quindi non rispedirai mai più al tuo ulteriore codice la risposta e normalmente RestClientResponseException diventa ResourceAccessException
MilacH

1
Dovresti controllare status==200, primaresponseCopy.getBody()
Anand Rockzz, il

4
Ma è un pacchetto privato. Hai inserito LoggingRequestInterceptor nel pacchetto "org.springframework.http.client"?
zbstof,

2
che dire asyncRestTemplate? Richiederebbe di restituire a ListenableFuturequando lo intercetti, che non è possibile modificare BufferingClientHttpResponseWrapperin un callback.
Ömer Faruk Almalı,

@ ÖmerFarukAlmalı In tal caso dovrai usare la catena o la trasformazione a seconda della versione di Guava che stai utilizzando. Vedi: stackoverflow.com/questions/8191891/…
James Watkins,

30

Puoi usare spring-rest-template-logger per registrare il RestTemplatetraffico HTTP.

Aggiungi una dipendenza al tuo progetto Maven:

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

Quindi personalizza RestTemplatecome segue:

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

Assicurarsi che la registrazione debug sia abilitata in application.properties:

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

Ora verrà registrato tutto il traffico HTTP RestTemplate org.hobsoft.spring.resttemplatelogger.LoggingCustomizer livello di debug.

NOTA BENE: ho scritto questa biblioteca.


Perché questa risposta è sottovalutata? Mi ha aiutato Grazie, @Mark Hobson.
Raffael Bechara Rameh,

3
Sono contento che abbia aiutato @RaffaelBecharaRameh. Inizialmente è stato sottoposto a downgrade perché non ho incorporato istruzioni dal progetto collegato. Sentiti libero di votare se l'hai trovato utile!
Mark Hobson,

Supportate via Gradle?
BlackHatSamurai,

1
@BlackHatSamurai spring-rest-template-logger è un normale artefatto di Maven, quindi dovrebbe funzionare bene con Gradle.
Mark Hobson,

1
Ciao @erhanasikoglu, prego! Esatto, puoi vederlo in uso qui: github.com/markhobson/spring-rest-template-logger/blob/master/…
Mark Hobson

29

La soluzione fornita da xenoterracide da usare

logging.level.org.apache.http=DEBUG

è buono ma il problema è che per impostazione predefinita Apache HttpComponents non viene utilizzato.

Per utilizzare Apache HttpComponents aggiungi al tuo pom.xml

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

e configurare RestTemplatecon:

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

Il modo più semplice, aggiungerò solo che non funziona con MockRestServiceServer, poiché sovrascrive requestFactory.
zbstof,

Funziona bene e senza problemi meno configurazione!
sunleo,

26

Alla fine ho trovato il modo di farlo nel modo giusto. La maggior parte della soluzione proviene da Come configuro Spring e SLF4J in modo da poter ottenere la registrazione?

Sembra che ci siano due cose che devono essere fatte:

  1. Aggiungi la seguente riga in log4j.properties: log4j.logger.httpclient.wire=DEBUG
  2. Assicurati che Spring non ignori la tua configurazione di registrazione

Il secondo problema si verifica principalmente negli ambienti primaverili in cui viene utilizzato slf4j (come nel mio caso). Pertanto, quando si utilizza slf4j, assicurarsi che accadano le seguenti due cose:

  1. Non ci sono librerie di log comuni nel tuo percorso di classe: questo può essere fatto aggiungendo i descrittori di esclusione nel tuo pom:

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
  2. Il file log4j.properties è memorizzato da qualche parte nel percorso di classe in cui Spring può trovarlo / vederlo. In caso di problemi, un'ultima soluzione sarebbe quella di inserire il file log4j.properties nel pacchetto predefinito (non è una buona pratica, ma solo per vedere che le cose funzionano come previsto)


7
Questo non funziona per me, ho fatto entrambe le cose. Non capisco perché devo inserire log4j.properties quando non viene comunque utilizzato nel mio progetto (controllato da mvn dependency: tree)
Pavel Niedoba,

Questo non funziona neanche per me. Ho anche provato a impostare il logger di root in modalità Debug e ancora niente.
James Watkins,

"httpclient.wire.content" e "httpclient.wire.header" sono nomi di logger dal framework Axis2. Possono essere utilizzati per registrare, ad esempio, le richieste SOAP in un progetto Spring se vengono eseguite utilizzando Axis2.
Lathspell

11
httpclient.wireproviene in realtà dalla libreria HttpConent di Apache HttpComponents (consultare hc.apache.org/httpcomponents-client-ga/logging.html ). Questa tecnica funzionerà solo se hai RestTemplateconfigurato l'uso diHttpComponentsClientHttpRequestFactory
Scott Frederick il

22

Riposo registrazione Modello

Opzione 1. Aprire la registrazione debug.

Configurare RestTemplate

  • Di default il RestTemplate si basa su servizi JDK standard per stabilire connessioni HTTP. È possibile passare a utilizzare una libreria HTTP diversa come Apache HttpComponents

    @Bean public RestTemplate restTemplate (costruttore RestTemplateBuilder) {RestTemplate restTemplate = builder.build (); rest restemplate; }

Configurare la registrazione

  • application.yml

    registrazione: livello: org.springframework.web.client.RestTemplate: DEBUG

Opzione 2. Utilizzo di Interceptor

Risposta wrapper

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

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

}

Configurare RestTemplate

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

Configurare la registrazione

  • Controllare il pacchetto di LoggingRestTemplate, ad esempio in application.yml :

    logging: livello: com.example.logging: DEBUG

Opzione 3. Utilizzo di httpcomponent

Importa la dipendenza httpcomponent

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

Configurare RestTemplate

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

Configurare la registrazione

  • Controllare il pacchetto di LoggingRestTemplate, ad esempio in application.yml :

    registrazione: livello: org.apache.http: DEBUG


Nota: se desideri configurare TestRestTemplate, configura RestTemplateBuilder: @Bean public RestTemplateBuilder restTemplateBuilder () {return new RestTemplateBuilder (). AdditionalInterceptors (Collections.singletonList (new LoggingRestTemplate ())); }
Kingoleg,

Si noti inoltre che il nuovo InputStreamReader (responseWrapper.getBody (), StandardCharsets.UTF_8)); può generare un errore se l '"altra estremità" ha restituito un errore. Potresti volerlo inserire in un blocco try.
PeterS

16

---- luglio 2019 ----

(usando Spring Boot)

Sono rimasto sorpreso dal fatto che Spring Boot, con tutta la sua magia Zero Configuration, non fornisca un modo semplice per ispezionare o registrare un semplice corpo di risposta JSON con RestTemplate. Ho esaminato le varie risposte e i commenti forniti qui e sto condividendo la mia versione distillata di ciò che (ancora) funziona e mi sembra una soluzione ragionevole, date le opzioni attuali (sto usando Spring Boot 2.1.6 con Gradle 4.4 )

1. Utilizzo di Fiddler come proxy http

Questa è in realtà una soluzione abbastanza elegante, in quanto ignora tutti gli ingombranti sforzi di creare il proprio intercettore o cambiare il client http sottostante in apache (vedi sotto).

Installa ed esegui Fiddler

e poi

aggiungi -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888alle Opzioni VM

2. Utilizzo di Apache HttpClient

Aggiungi Apache HttpClient alle dipendenze Maven o Gradle.

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

Utilizzare HttpComponentsClientHttpRequestFactorycome RequestFactory per RestTemplate. Il modo più semplice per farlo sarebbe:

RestTemplate restTemplate = new RestTemplate();

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Abilita DEBUG nel tuo application.propertiesfile (se stai usando Spring Boot)

logging.level.org.apache.http=DEBUG

Se si utilizza Spring Boot, è necessario assicurarsi di avere un framework di registrazione impostato, ad esempio utilizzando una dipendenza spring-boot-starter che includa spring-boot-starter-logging .

3. Utilizzare un intercettore

Ti lascerò leggere le proposte, le controproposte e i gotcha nelle altre risposte e commenti e deciderò tu stesso se vuoi seguire questa strada.

4. Registra l'URL e lo stato della risposta senza corpo

Sebbene questo non soddisfi i requisiti dichiarati di registrazione del corpo, è un modo rapido e semplice per iniziare a registrare le chiamate REST. Visualizza l'URL completo e lo stato della risposta.

Aggiungi semplicemente la seguente riga al tuo application.propertiesfile (supponendo che tu stia utilizzando Spring Boot e supponendo che tu stia utilizzando una dipendenza di avvio di Spring Boot che include spring-boot-starter-logging)

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

L'output sarà simile al seguente:

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]

2
No. 4 è il modo più semplice per eseguire il debug.
Yubaraj,

1
No. 2 ha funzionato per me. Registra il corpo della richiesta. Grazie!
caglar

1
Ho trovato il n. 3 un modo semplice per farlo quando sono arrivato a questo problema.
Bill Naylor

12

Oltre alla registrazione HttpClient descritta nell'altra risposta , è anche possibile introdurre un ClientHttpRequestInterceptor che legge il corpo della richiesta e la risposta e la registra. Potresti voler fare questo se altre cose usano anche HttpClient o se desideri un formato di registrazione personalizzato. Attenzione: vorrai assegnare a RestTemplate un BufferingClientHttpRequestFactory in modo da poter leggere la risposta due volte.


12

Come indicato nelle altre risposte, l'organismo di risposta necessita di un trattamento speciale in modo che possa essere letto ripetutamente (per impostazione predefinita, i suoi contenuti vengono consumati alla prima lettura).

Invece di utilizzare il BufferingClientHttpRequestFactorydurante l'impostazione della richiesta, l'interceptor stesso può racchiudere la risposta e assicurarsi che il contenuto sia conservato e possa essere letto ripetutamente (dal logger e dal consumatore della risposta):

Il mio intercettore, che

  • buffer il corpo della risposta utilizzando un wrapper
  • registra in modo più compatto
  • registra anche l' identificatore del codice di stato (ad es. 201 creato)
  • include un numero di sequenza della richiesta che consente di distinguere facilmente le voci di registro simultanee da più thread

Codice:

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

Configurazione:

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

Esempio di output del registro:

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

1
Questo funziona con la versione Spring 2019 mantenendo intatto il corpo.
Udo tenuto il

1
Funziona sulla primavera 2.1.10 :) Grazie
Moler

8

application.properties

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

application.yml

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

8

Questo potrebbe non essere il modo corretto di farlo, ma penso che questo sia l'approccio più semplice per stampare richieste e risposte senza riempire troppo i registri.

Aggiungendo al di sotto di 2 righe application.properties registra tutte le richieste e le risposte 1a riga per registrare le richieste e 2a riga per registrare le risposte.

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

La registrazione delle risposte non funziona per me. Registra solo Statuscode. Dovrebbe registrare il payload?
badera,

La classe HttpEntityMethodProcessor (v5.1.8) non registra nulla.
Chris,

6

Supponendo che RestTemplatesia configurato per utilizzare HttpClient 4.x, è possibile leggere qui la documentazione di registrazione di HttpClient . I logger sono diversi da quelli specificati nelle altre risposte.

La configurazione della registrazione per HttpClient 3.x è disponibile qui .


4

Quindi molte risposte qui richiedono modifiche alla codifica e classi personalizzate e non è davvero necessario. Gte un proxy di debug come fiddler e imposta il tuo ambiente Java per utilizzare il proxy sulla riga di comando (-Dhttp.proxyHost e -Dhttp.proxyPort) quindi esegui fiddler e puoi vedere le richieste e le risposte nella loro interezza. Presenta inoltre numerosi vantaggi accessori come la capacità di armeggiare con i risultati e le risposte prima e dopo che sono stati inviati per eseguire esperimenti prima di impegnarsi nella modifica del server.

L'ultimo bit di un problema che può sorgere è se è necessario utilizzare HTTPS, sarà necessario esportare il certificato SSL dal violinista e importarlo nel keystore java (cacerts) suggerimento: la password predefinita del keystore java di solito è "changeit".


1
Questo ha funzionato per me usando intellij e l'installazione regolare di violino. Ho modificato la configurazione di esecuzione e impostare le opzioni di VM a -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888.
JD

Grazie! Questa è una soluzione piuttosto elegante rispetto alla scrittura del proprio Interceptor.
Chris,

3

Per accedere a Logback con l'aiuto di Apache HttpClient:

È necessario Apache HttpClient nel percorso di classe:

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

Configura il tuo RestTemplateutilizzo di HttpClient:

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Per registrare richieste e risposte, aggiungere al file di configurazione di Logback:

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

O per accedere ancora di più:

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

Quale file di configurazione del logback?
G_V

1
@G_V logback.xml o logback-test.xml per i test.
holmis83

Funziona anche con il org.apache.http.wire=DEBUGtuo application.propertiesnow
G_V

@G_V se si utilizza Spring-Boot. La mia risposta funziona senza avvio.
holmis83,

2

Il trucco di configurare il tuo RestTemplatecon a BufferingClientHttpRequestFactorynon funziona se ne stai usando uno ClientHttpRequestInterceptor, cosa che farai se stai tentando di accedere tramite intercettori. Ciò è dovuto al modo in cui InterceptingHttpAccessor(cheRestTemplate sottoclassi) funziona.

Per RestTemplatefarla breve ... basta usare questa classe al posto di (notare che utilizza l'API di registrazione SLF4J, modificarla secondo necessità):

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

Sono d'accordo che è sciocco che ci vuole così tanto lavoro solo per farlo.


2

Aggiungendo alla discussione sopra questo rappresenta solo scenari felici. probabilmente non sarai in grado di registrare la risposta in caso di errore .

In questo caso, oltre a tutti i casi precedenti, è necessario sovrascrivere DefaultResponseErrorHandler e impostarlo come di seguito

restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());

2

Stranamente, nessuna di queste soluzioni funziona come RestTemplate non sembra restituire la risposta su alcuni errori client e server 500x. In tal caso, avrai a disposizione anche quelli implementando ResponseErrorHandler come segue. Ecco una bozza di codice, ma ottieni il punto:

È possibile impostare lo stesso intercettore del gestore errori:

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

E l'intercettazione implementa entrambe le interfacce:

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

Che cosa succede se body è multipart / form-data, esiste un modo semplice per filtrare i dati binari (contenuto del file) dal registro?
Luca

1

Come sottolineato da @MilacH, c'è un errore nell'implementazione. Se viene restituito statusCode> 400, viene generata una IOException, poiché errorHandler non viene invocato, dagli intercettori. L'eccezione può essere ignorata e quindi catturata nuovamente nel metodo del gestore.

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

}

0

La migliore soluzione ora, basta aggiungere dipendenza:

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

Contiene una classe LoggingRequestInterceptor che puoi aggiungere in questo modo al tuo RestTemplate:

integrare questa utilità aggiungendola come intercettore a un RestTemplate a molla, nel modo seguente:

restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build());

e aggiungi un'implementazione di slf4j al tuo framework come log4j.

o utilizzare direttamente "Zg2proRestTemplate" . La "migliore risposta" di @PaulSabou sembra così, dato che le librerie httpclient e tutte le librerie apache.http non vengono necessariamente caricate quando si utilizza un RestTemplate di primavera.


qual è la versione rilasciata?
popalka,

la versione rilasciata ora è 0.2
Moses Meyer,

1
la facilità d'uso è ottima, ma manca di intestazioni
WrRaThY

inoltre: tutti i metodi utili in LoggingRequestInterceptor sono privati, il che è un problema quando si tratta di estensione (potrebbe essere protetto)
WrRaThY

purtroppo, non riesco a modificare i commenti dopo 5 minuti. Tutto quello che devi fare per accedere alle intestazioni è questo: log("Headers: {}", request.headers)dentro LoggingRequestInterceptor:traceRequeste log("Headers: {}", response.headers)dentro LoggingRequestInterceptor:logResponse. Potresti voler pensare di aggiungere alcuni flag per registrare intestazioni e corpo. Inoltre, potresti voler controllare il tipo di contenuto del corpo per la registrazione (ad esempio, registra solo application / json *). Questo dovrebbe essere anche configurabile. tutto sommato, con quelle piccole modifiche avrai una bella libreria da diffondere. buon lavoro :)
WrRaThY

0

Volevo aggiungere anche la mia implementazione di questo. Mi scuso per tutti i punti e virgola mancanti, questo è scritto in Groovy.

Avevo bisogno di qualcosa di più configurabile della risposta accettata fornita. Ecco un bean modello di riposo che è molto agile e registra tutto ciò che l'OP sta cercando.

Classe intercettore registrazione personalizzata:

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

Definizione di fagiolo modello di riposo:

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

Implementazione:

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


0

org.apache.http.wire fornisce registri troppo illeggibili, quindi utilizzo il registro per registrare l'applicazione Servlet e RestTemplate req / resp per accedere

build.gradle

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

application.properties

logging.level.org.zalando.logbook:TRACE

RestTemplate

@Configuration
public class RestTemplateConfig {

@Autowired
private LogbookHttpRequestInterceptor logbookHttpRequestInterceptor;

@Autowired
private LogbookHttpResponseInterceptor logbookHttpResponseInterceptor;

@Bean
public RestTemplate restTemplate() {
    return new 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();
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(client);
        return clientHttpRequestFactory;
    }

}
}

-1

In relazione alla risposta utilizzando ClientHttpInterceptor, ho trovato un modo per mantenere l'intera risposta senza buffer di produzione. Memorizza semplicemente il flusso di input del corpo della risposta all'interno dell'array di byte utilizzando un metodo utils che copierà l'array dal corpo, ma è importante circondare questo metodo con try catch perché si interromperà se la risposta è vuota (che è la causa dell'eccezione di accesso alle risorse) e in cattività basta creare un array di byte vuoto e creare semplicemente una classe interna anonima di ClientHttpResponse utilizzando tale array e altri parametri dalla risposta originale. Quindi puoi restituire quel nuovo oggetto ClientHttpResponse alla catena di esecuzione del modello restante e puoi registrare la risposta usando l'array di byte del corpo che è stato precedentemente memorizzato. In questo modo eviterai di utilizzare InputStream nella risposta effettiva e puoi utilizzare la risposta del modello di riposo così com'è. Nota,


-2

il mio logger config ha usato xml

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

quindi otterrai qualcosa come di seguito:

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]

tramite HttpMessageConverterExtractor.java:92, è necessario continuare il debug e, nel mio caso, ho ottenuto questo:

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

e questo:

outputMessage.getBody().flush();

outputMessage.getBody () contiene il messaggio http (tipo di post) che invia


la registrazione delle tracce potrebbe essere troppo dettagliata ... e se ci fossero migliaia di richieste al secondo ??
Gervasio Amy,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.