Spring RestTemplate - comment activer le débogage / enregistrement complet des demandes / réponses?


221

J'utilise Spring RestTemplate depuis un certain temps et je frappe constamment un mur lorsque j'essaie de déboguer ses demandes et réponses. Je cherche essentiellement à voir les mêmes choses que je vois lorsque j'utilise curl avec l'option "verbose" activée. Par exemple :

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

Afficherait à la fois les données envoyées et les données reçues (y compris les en-têtes, les cookies, etc.).

J'ai vérifié certains articles connexes tels que: comment enregistrer la réponse dans Spring RestTemplate? mais je n'ai pas réussi à résoudre ce problème.

Une façon de le faire serait de changer le code source de RestTemplate et d'y ajouter des instructions de journalisation supplémentaires, mais je trouverais cette approche vraiment une solution de dernier recours. Il devrait y avoir un moyen de dire à Spring Web Client / RestTemplate de tout enregistrer de manière beaucoup plus conviviale.

Mon objectif serait de pouvoir le faire avec du code comme:

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

puis pour obtenir le même type d'informations de débogage (que j'obtiens avec curl) dans le fichier journal ou dans la console. Je pense que cela serait extrêmement utile pour quiconque utilise le Spring RestTemplate et a des problèmes. L'utilisation de curl pour déboguer vos problèmes RestTemplate ne fonctionne tout simplement pas (dans certains cas).


31
Avertissement à tous ceux qui liront en 2018: il n'y a pas de réponse simple à cela!
davidfrancis

3
Le moyen le plus simple consiste à utiliser un point d'arrêt dans la méthode d'écriture (...) de la classe AbstractHttpMessageConverter, il existe un objet outputMessage où vous pouvez voir les données. PS Vous pouvez copier la valeur puis la formater avec un formateur en ligne.
Sergey Chepurnov

1
semble que cela devrait être facile à faire au printemps, mais, à en juger par les réponses ici - pas le cas. Une autre solution serait donc de contourner complètement Spring et d'utiliser un outil comme Fiddler pour capturer la demande / réponse.
michaelok

lire la réponse à cette question à partir du lien suivant: spring-resttemplate-how-to-enable-full-debugging-logging-of-
requests-answers

Juillet 2019: Comme il n'y a toujours pas de solution simple à cette question, j'ai essayé de donner un résumé des 24 autres réponses (jusqu'à présent) et de leurs commentaires et discussions dans ma propre réponse ci-dessous . J'espère que ça aide.
Chris

Réponses:


208

Juste pour compléter l'exemple avec une implémentation complète de ClientHttpRequestInterceptorpour tracer la demande et la réponse:

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

}

Instanciez ensuite en RestTemplateutilisant a BufferingClientHttpRequestFactoryet le LoggingRequestInterceptor:

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

Le BufferingClientHttpRequestFactoryest requis car nous voulons utiliser le corps de réponse à la fois dans l'intercepteur et pour le code d'appel initial. L'implémentation par défaut permet de lire le corps de réponse une seule fois.


28
C'est faux. Si vous lisez le flux, le code d'application ne pourra pas lire la réponse.
James Watkins

28
nous avons donné au RestTemplate un BufferingClientHttpRequestFactory afin que nous puissions lire la réponse deux fois.
sofiene zaghdoudi

16
Nous utilisons cette technique depuis environ 3 mois maintenant. Cela ne fonctionne qu'avec RestTemplate configuré avec un BufferingClientHttpResponseWrappercomme @sofienezaghdoudi l'implique. Cependant, cela ne fonctionne pas lorsqu'il est utilisé dans des tests utilisant le framework mockServer de Spring, car il MockRestServiceServer.createServer(restTemplate)écrase RequestFactory dans InterceptingClientHttpRequestFactory.
RubesMN

8
La technique est bonne, la mise en œuvre est mauvaise. 404 case, response.getBody () throw IOException -> vous ne récupérez jamais le journal et même pire, il deviendra une ResourceAccessException dans votre code suivant, au lieu d'une RestClientResponseException
MilacH

6
Merci pour la réponse. Mais c'est une mauvaise pratique d'avoir plusieurs "log.debug" car cela pourrait être réparti sur beaucoup d'autres journaux. Il est préférable d'utiliser une seule instruction log.debug pour être sûr que tout est au même endroit
user2447161

130

dans Spring Boot, vous pouvez obtenir la demande / réponse complète en définissant cela dans les propriétés (ou une autre méthode à 12 facteurs)

logging.level.org.apache.http=DEBUG

cela produit

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

et réponse

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

ou logging.level.org.apache.http.wire=DEBUGqui semble contenir toutes les informations pertinentes


4
C'était la chose la plus simple qui faisait ce que je voulais. J'encourage fortement à inclure cela dans la réponse acceptée.
michaelavila

22
Selon le javadoc de 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 n'utilise pas ces classes Apache par défaut, comme l'a souligné @OrtomalaLokni, vous devez donc également indiquer comment les utiliser en plus de la façon d'imprimer le débogage lorsqu'elles sont utilisées.
Captain Man

Je reçois comme ça:http-outgoing-0 << "[0x1f][0x8b][0x8][0x0][0x0][0x0][0x0][0x0]
Partha Sarathi Ghosh

2
@ParthaSarathiGhosh Le contenu est probablement encodé en gzip, c'est pourquoi vous ne voyez pas le texte brut.
Matthew Buckett

79

Extension de la réponse @hstoerr avec du code:


Créer LoggingRequestInterceptor pour enregistrer les réponses aux demandes

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

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

Ceci n'est pas disponible avant la version Spring-3.1.
Gyan

3
il ne répond pas à la question de «réponse de journalisation», mais laisse un commentaire // do logging à la place.
Jiang YD,

1
faire la journalisation était facile, mais cela ne fonctionne que pour les demandes, je ne vois pas de corps de réponse, supposons que j'ai un objet de réponse, mais lire son flux n'est pas une bonne idée.
Pavel Niedoba

11
@PavelNiedoba Le BufferClientHttpRequestFactory permet de lire la réponse plusieurs fois.
mjj1409

2
Cela fonctionne bien si vous avez besoin de stocker des informations sur la demande / réponse dans une base de données pour le débogage et la journalisation régulière ne convient pas à vos besoins.
GameSalutes

32

Votre meilleur pari est d'ajouter logging.level.org.springframework.web.client.RestTemplate=DEBUGau application.propertiesfichier.

D'autres solutions comme la configuration log4j.logger.httpclient.wirene fonctionneront pas toujours car elles supposent que vous utilisez log4jet Apache HttpClient, ce qui n'est pas toujours vrai.

Notez cependant que cette syntaxe ne fonctionnera que sur les dernières versions de Spring Boot.


5
Il ne s'agit pas de consigner le corps de la demande et de la réponse, juste l'url et le type de demande (spring-web-4.2.6)
dve

1
Vous avez raison, ce n'est pas une wirejournalisation, elle ne comprend que des informations essentielles comme l'URL, le code resepone, les paramètres POST, etc.
gamliela

1
ce que vous voulez vraiment, c'est ce stackoverflow.com/a/39109538/206466
xenoterracide

C'est très bien mais le corps de la réponse n'a pas pu être vu!
sunleo

Brillant. Bien qu'il n'imprime pas le corps de la réponse, il est toujours très utile. Merci.
Chris

30

Aucune de ces réponses ne résout réellement 100% du problème. mjj1409 obtient l'essentiel, mais évite commodément le problème de la journalisation de la réponse, ce qui prend un peu plus de travail. Paul Sabou fournit une solution qui semble réaliste, mais ne fournit pas suffisamment de détails pour être réellement mise en œuvre (et cela n'a pas fonctionné du tout pour moi). Sofiene a obtenu la journalisation mais avec un problème critique: la réponse n'est plus lisible car le flux d'entrée a déjà été consommé!

Je recommande d'utiliser un BufferingClientHttpResponseWrapper pour encapsuler l'objet de réponse pour permettre la lecture du corps de réponse plusieurs fois:

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

}

Cela ne consommera pas InputStream car le corps de la réponse est chargé en mémoire et peut être lu plusieurs fois. Si vous n'avez pas le BufferingClientHttpResponseWrapper sur votre chemin de classe, vous pouvez trouver l'implémentation simple ici:

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

Pour configurer le RestTemplate:

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

même, responseCopy.getBody () lève IOexception dans le cas de 404, donc vous ne renvoyez jamais à votre code supplémentaire la réponse et normalement RestClientResponseException devient une ResourceAccessException
MilacH

1
Vous devriez vérifier status==200avantresponseCopy.getBody()
Anand Rockzz

4
Mais c'est un paquet privé. Avez-vous mis votre LoggingRequestInterceptor dans le package 'org.springframework.http.client'?
zbstof

2
qu'en est-il asyncRestTemplate? Il faudrait retourner un ListenableFuturelorsque vous l'interceptez, ce qui n'est pas possible de modifier avec BufferingClientHttpResponseWrapperdans un rappel.
Ömer Faruk Almalı

@ ÖmerFarukAlmalı Dans ce cas, vous devrez utiliser la chaîne ou la transformer selon la version de Guava que vous utilisez. Voir: stackoverflow.com/questions/8191891/…
James Watkins

30

Vous pouvez utiliser Spring-rest-template-logger pour enregistrer le RestTemplatetrafic HTTP.

Ajoutez une dépendance à votre projet Maven:

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

Personnalisez ensuite votre RestTemplatecomme suit:

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

Assurez-vous que la journalisation du débogage est activée dans application.properties:

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

Désormais, tout le trafic HTTP RestTemplate sera enregistré org.hobsoft.spring.resttemplatelogger.LoggingCustomizerau niveau du débogage.

AVERTISSEMENT: j'ai écrit cette bibliothèque.


Pourquoi cette réponse est-elle rejetée? Ça m'a aidé. Merci, @Mark Hobson.
Raffael Bechara Rameh

3
Heureux que cela ait aidé @RaffaelBecharaRameh. Il a été initialement rétrogradé parce que je n'ai pas intégré les instructions du projet lié. N'hésitez pas à voter si vous l'avez trouvé utile!
Mark Hobson

Soutenez-vous via Gradle?
BlackHatSamurai

1
@BlackHatSamurai spring-rest-template-logger est un artefact Maven régulier, donc cela devrait fonctionner correctement avec Gradle.
Mark Hobson

1
Salut @erhanasikoglu, vous êtes les bienvenus! C'est vrai, vous pouvez le voir en cours d'utilisation ici: github.com/markhobson/spring-rest-template-logger/blob/master/…
Mark Hobson

29

La solution donnée par le xénoterracide à utiliser

logging.level.org.apache.http=DEBUG

est bon mais le problème est que par défaut Apache HttpComponents n'est pas utilisé.

Pour utiliser Apache HttpComponents, ajoutez à votre pom.xml

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

et configurer RestTemplateavec:

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

Plus simple, j'ajouterai seulement que cela ne fonctionne pas avec MockRestServiceServer, car il écrase requestFactory.
zbstof

Fonctionne bien et pas de problèmes moins de configuration!
sunleo

26

J'ai finalement trouvé un moyen de le faire de la bonne façon. La plupart de la solution provient de Comment configurer Spring et SLF4J pour pouvoir me connecter?

Il semble qu'il y ait deux choses à faire:

  1. Ajoutez la ligne suivante dans log4j.properties: log4j.logger.httpclient.wire=DEBUG
  2. Assurez-vous que le printemps n'ignore pas votre configuration de journalisation

Le deuxième problème se produit principalement dans les environnements de printemps où slf4j est utilisé (comme c'était mon cas). En tant que tel, lorsque slf4j est utilisé, assurez-vous que les deux choses suivantes se produisent:

  1. Il n'y a pas de bibliothèque de journalisation des biens communs dans votre chemin de classe: cela peut être fait en ajoutant les descripteurs d'exclusion dans votre pom:

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
  2. Le fichier log4j.properties est stocké quelque part dans le chemin de classe où le printemps peut le trouver / le voir. Si vous avez des problèmes avec cela, une solution de dernier recours serait de placer le fichier log4j.properties dans le package par défaut (ce n'est pas une bonne pratique mais juste pour voir que les choses fonctionnent comme prévu)


7
Ça ne marche pas pour moi, j'ai fait les deux choses. Je ne comprends pas pourquoi dois-je mettre log4j.properties quand il n'est pas utilisé de toute façon dans mon projet (vérifié par la dépendance mvn: tree)
Pavel Niedoba

Cela ne fonctionne pas non plus pour moi. J'ai même essayé de mettre l'enregistreur racine en mode débogage et toujours rien.
James Watkins

"httpclient.wire.content" et "httpclient.wire.header" sont des noms d'enregistreurs du framework Axis2. Ils peuvent être utilisés pour enregistrer, par exemple, des demandes SOAP dans un projet Spring si celles-ci sont effectuées à l'aide d'Axis2.
lathspell

11
httpclient.wireprovient en fait de la bibliothèque Apache HttpComponents HttpClient (voir hc.apache.org/httpcomponents-client-ga/logging.html ). Cette technique ne fonctionnera que si vous avez RestTemplateconfiguré pour utiliser leHttpComponentsClientHttpRequestFactory
Scott Frederick

22

Journalisation de RestTemplate

Option 1. Ouvrez la journalisation du débogage.

Configurer RestTemplate

  • Par défaut, le RestTemplate s'appuie sur des fonctionnalités JDK standard pour établir des connexions HTTP. Vous pouvez basculer pour utiliser une autre bibliothèque HTTP telle que Apache HttpComponents

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

Configurer la journalisation

  • application.yml

    journalisation: niveau: org.springframework.web.client.RestTemplate: DEBUG

Option 2. Utilisation d'Interceptor

Réponse de 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();
    }
}

Implémenter 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;
    }

}

Configurer RestTemplate

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

Configurer la journalisation

  • Vérifiez le package de LoggingRestTemplate, par exemple dans application.yml:

    journalisation: niveau: com.example.logging: DEBUG

Option 3. Utilisation de httpcomponent

Importer la dépendance httpcomponent

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

Configurer RestTemplate

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

Configurer la journalisation

  • Vérifiez le package de LoggingRestTemplate, par exemple dans application.yml:

    journalisation: niveau: org.apache.http: DEBUG


Remarque: si vous souhaitez configurer TestRestTemplate, configurez RestTemplateBuilder: @Bean public RestTemplateBuilder restTemplateBuilder () {return new RestTemplateBuilder (). AdditionalInterceptors (Collections.singletonList (new LoggingRestTemplate ())); }
kingoleg

Notez également que le nouveau InputStreamReader (responseWrapper.getBody (), StandardCharsets.UTF_8)); peut renvoyer une erreur si "l'autre extrémité" a renvoyé une erreur. Vous pouvez le placer dans un bloc d'essai.
PeterS

16

---- juillet 2019 ----

(en utilisant Spring Boot)

J'ai été surpris que Spring Boot, avec toute sa magie de configuration zéro, ne fournisse pas un moyen facile d'inspecter ou de consigner un corps de réponse JSON simple avec RestTemplate. J'ai parcouru les différentes réponses et commentaires fournis ici, et partage ma propre version distillée de ce qui fonctionne (encore) et me semble être une solution raisonnable, compte tenu des options actuelles (j'utilise Spring Boot 2.1.6 avec Gradle 4.4 )

1. Utilisation de Fiddler comme proxy http

C'est en fait une solution assez élégante, car elle contourne tous les efforts fastidieux de création de votre propre intercepteur ou de modification du client http sous-jacent en apache (voir ci-dessous).

Installer et exécuter Fiddler

puis

ajouter -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888à vos options VM

2. Utilisation d'Apache HttpClient

Ajoutez Apache HttpClient à vos dépendances Maven ou Gradle.

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

À utiliser HttpComponentsClientHttpRequestFactorycomme RequestFactory pour RestTemplate. La façon la plus simple de le faire serait:

RestTemplate restTemplate = new RestTemplate();

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Activez DEBUG dans votre application.propertiesfichier (si vous utilisez Spring Boot)

logging.level.org.apache.http=DEBUG

Si vous utilisez Spring Boot, vous devrez vous assurer que vous disposez d'un cadre de journalisation, par exemple en utilisant une dépendance Spring-Boot-Starter qui inclut spring-boot-starter-logging.

3. Utilisez un intercepteur

Je vais vous laisser lire les propositions, contre-propositions et accrochages dans les autres réponses et commentaires et décider par vous-même si vous voulez aller dans cette direction.

4. Consigner l'URL et l'état de réponse sans corps

Bien que cela ne réponde pas aux exigences déclarées de journalisation du corps, c'est un moyen rapide et simple de commencer à journaliser vos appels REST. Il affiche l'URL complète et l'état de la réponse.

Ajoutez simplement la ligne suivante à votre application.propertiesfichier (en supposant que vous utilisez Spring Boot et en supposant que vous utilisez une dépendance de démarrage Spring Spring qui inclut spring-boot-starter-logging)

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

La sortie ressemblera à ceci:

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
Le numéro 4 est le moyen le plus simple de déboguer.
Yubaraj

1
Le numéro 2 a fonctionné pour moi. Il enregistre le corps de la demande. Je vous remercie!
caglar

1
J'ai trouvé le numéro 3 un moyen facile de le faire lorsque je suis arrivé à ce problème.
Bill Naylor

12

Outre la journalisation HttpClient décrite dans l'autre réponse , vous pouvez également introduire un ClientHttpRequestInterceptor qui lit le corps de la demande et de la réponse et l'enregistre. Vous souhaiterez peut-être le faire si d'autres éléments utilisent également HttpClient ou si vous souhaitez un format de journalisation personnalisé. Attention: vous souhaiterez donner au RestTemplate un BufferingClientHttpRequestFactory de sorte que vous puissiez lire la réponse deux fois.


12

Comme indiqué dans les autres réponses, le corps de la réponse a besoin d'un traitement spécial pour pouvoir être lu à plusieurs reprises (par défaut, son contenu est consommé lors de la première lecture).

Au lieu d'utiliser le BufferingClientHttpRequestFactorylors de la configuration de la demande, l'intercepteur lui-même peut encapsuler la réponse et s'assurer que le contenu est conservé et peut être lu à plusieurs reprises (par l'enregistreur ainsi que par le consommateur de la réponse):

Mon intercepteur, qui

  • met en mémoire tampon le corps de la réponse à l' aide d'un wrapper
  • se connecte de manière plus compacte
  • enregistre également l' identifiant du code d'état (par exemple 201 créé)
  • comprend un numéro de séquence de demande permettant de distinguer facilement les entrées de journal simultanées de plusieurs 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()));
    }

Exemple de sortie de journal:

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
Celui-ci fonctionne avec la version printemps 2019 en gardant le corps intact.
Udo tenue le

1
Fonctionne sur Spring 2.1.10 :) Merci
Moler

8

application.properties

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

application.yml

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

8

Ce n'est peut-être pas la bonne façon de procéder, mais je pense que c'est l'approche la plus simple pour imprimer les demandes et les réponses sans trop remplir de journaux.

En ajoutant ci-dessous 2 lignes, application.properties enregistre toutes les demandes et réponses en 1ère ligne afin d'enregistrer les demandes et en 2e ligne pour enregistrer les réponses.

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

L'enregistrement des réponses ne fonctionne pas pour moi. Il enregistre simplement Statuscode. Doit-il enregistrer la charge utile?
badera

La classe HttpEntityMethodProcessor (v5.1.8) ne consigne rien.
Chris

6

En supposant qu'il RestTemplateest configuré pour utiliser HttpClient 4.x, vous pouvez lire ici la documentation de journalisation de HttpClient . Les enregistreurs sont différents de ceux spécifiés dans les autres réponses.

La configuration de journalisation pour HttpClient 3.x est disponible ici .


4

De nombreuses réponses ici nécessitent des modifications de codage et des classes personnalisées et ce n'est vraiment pas nécessaire. Gte un proxy de débogage tel que fiddler et configurez votre environnement java pour utiliser le proxy sur la ligne de commande (-Dhttp.proxyHost et -Dhttp.proxyPort) puis exécutez fiddler et vous pouvez voir les demandes et les réponses dans leur intégralité. Il présente également de nombreux avantages auxiliaires tels que la possibilité de bricoler les résultats et les réponses avant et après leur envoi pour exécuter des expériences avant de s'engager dans la modification du serveur.

Le dernier problème qui peut survenir est que si vous devez utiliser HTTPS, vous devrez exporter le certificat SSL de fiddler et l'importer dans le magasin de clés java (cacerts): le mot de passe par défaut du magasin de java est généralement "changeit".


1
Cela a fonctionné pour moi en utilisant intellij et l'installation régulière de violon. J'ai modifié la configuration d'exécution et défini les options de la machine virtuelle sur -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888.
JD

Merci! C'est une solution assez élégante par rapport à l'écriture de votre propre intercepteur.
Chris

3

Pour vous connecter à Logback avec l'aide d'Apache HttpClient:

Vous avez besoin d'Apache HttpClient dans classpath:

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

Configurez votre RestTemplatepour utiliser HttpClient:

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Pour enregistrer les demandes et les réponses, ajoutez au fichier de configuration Logback:

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

Ou pour vous connecter encore plus:

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

Quel fichier de configuration de déconnexion?
G_V

1
@G_V logback.xml ou logback-test.xml pour les tests.
holmis83

Il fonctionne également avec org.apache.http.wire=DEBUGvotre application.propertiesmaintenant
G_V

@G_V si vous utilisez Spring-Boot. Ma réponse fonctionne sans Boot.
holmis83

2

L'astuce de configuration de votre RestTemplateavec un BufferingClientHttpRequestFactoryne fonctionne pas si vous en utilisez un ClientHttpRequestInterceptor, ce que vous ferez si vous essayez de vous connecter via des intercepteurs. Cela est dû à la façon dont InterceptingHttpAccessor(quelles RestTemplatesous-classes) fonctionnent.

Pour faire court ... utilisez simplement cette classe à la place de RestTemplate(notez que cela utilise l'API de journalisation SLF4J, modifiez-la si nécessaire):

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

Je suis d'accord que c'est idiot qu'il faut autant de travail juste pour le faire.


2

Ajoutant à la discussion ci-dessus, cela ne représente que des scénarios heureux. vous ne pourrez probablement pas enregistrer la réponse si une erreur survient.

Dans ce cas, plus tous les cas ci-dessus, vous devez remplacer DefaultResponseErrorHandler et le définir comme ci-dessous

restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());

2

Étrangement, aucune de ces solutions ne fonctionne car RestTemplate ne semble pas renvoyer la réponse à certaines erreurs 500x du client et du serveur. Dans ce cas, vous devrez également les enregistrer en implémentant ResponseErrorHandler comme suit. Voici un projet de code, mais vous obtenez le point:

Vous pouvez définir le même intercepteur que le gestionnaire d'erreurs:

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

Et l'interception implémente les deux 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);
    }
}

Que se passe-t-il si le corps est constitué de plusieurs parties / données de formulaire, existe-t-il un moyen simple de filtrer les données binaires (contenu du fichier) du journal?
Luke

1

Comme l'a souligné @MilacH, il y a une erreur dans l'implémentation. Si un statusCode> 400 est renvoyé, une IOException est levée, car le gestionnaire d'erreur n'est pas appelé, à partir des intercepteurs. L'exception peut être ignorée et est ensuite interceptée à nouveau dans la méthode du gestionnaire.

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

Meilleure solution maintenant, ajoutez simplement la dépendance:

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

Il contient une classe LoggingRequestInterceptor que vous pouvez ajouter de cette façon à votre RestTemplate:

intégrer cet utilitaire en l'ajoutant en tant qu'intercepteur à un RestTemplate à ressort, de la manière suivante:

restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build());

et ajoutez une implémentation slf4j à votre framework comme log4j.

ou utilisez directement "Zg2proRestTemplate" . La "meilleure réponse" de @PaulSabou semble ainsi, car httpclient et toutes les bibliothèques apache.http ne sont pas nécessairement chargées lors de l'utilisation d'un Spring RestTemplate.


quelle est la version publiée?
popalka

la version publiée est maintenant 0,2
Moses Meyer

1
la facilité d'utilisation est excellente, mais il manque des en
WrRaThY

en outre: toutes les méthodes utiles dans LoggingRequestInterceptor sont privées, ce qui est un problème en ce qui concerne l'extension (pourrait être protégé)
WrRaThY

malheureusement, je ne peux pas modifier les commentaires après 5 minutes. Tout ce que vous avez à faire pour enregistrer les en-têtes est le suivant: log("Headers: {}", request.headers)in LoggingRequestInterceptor:traceRequestand log("Headers: {}", response.headers)in LoggingRequestInterceptor:logResponse. Vous voudrez peut-être penser à ajouter des indicateurs pour la journalisation des en-têtes et du corps. En outre, vous souhaiterez peut-être vérifier le type de contenu du corps pour la journalisation (par exemple, journaliser uniquement l'application / json *). Cela devrait également être configurable. dans l'ensemble, avec ces petits ajustements, vous aurez une belle bibliothèque à diffuser. bon travail :)
WrRaThY

0

Je voulais également ajouter ma mise en œuvre de cela. Je m'excuse pour tous les points-virgules manquants, c'est écrit en Groovy.

J'avais besoin de quelque chose de plus configurable que la réponse acceptée fournie. Voici un bean de modèle de repos qui est très agile et enregistrera tout ce que l'OP recherche.

Classe d'interception de journalisation personnalisée:

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

Définition du bean Rest Template:

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

La mise en oeuvre:

@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 donne des journaux trop illisibles, donc j'utilise logbook pour enregistrer l'application Servlet et RestTemplate req / resp pour se connecter

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

En ce qui concerne la réponse à l'aide de ClientHttpInterceptor, j'ai trouvé un moyen de conserver la réponse entière sans tamponner les usines. Il suffit de stocker le flux d'entrée du corps de réponse dans le tableau d'octets à l'aide d'une méthode utils qui copiera ce tableau du corps, mais important, entourez cette méthode de try catch car elle se cassera si la réponse est vide (c'est la cause de l'exception d'accès aux ressources) et dans catch, créez simplement un tableau d'octets vide et créez simplement une classe interne anonyme de ClientHttpResponse en utilisant ce tableau et d'autres paramètres de la réponse d'origine. Vous pouvez ensuite renvoyer ce nouvel objet ClientHttpResponse à la chaîne d'exécution du modèle de repos et vous pouvez consigner la réponse à l'aide du tableau d'octets du corps qui est précédemment stocké. De cette façon, vous éviterez de consommer InputStream dans la réponse réelle et vous pourrez utiliser la réponse Rest Template comme elle est. Remarque,


-2

ma configuration de l'enregistreur a utilisé xml

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

alors vous obtiendrez quelque chose comme ci-dessous:

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]

via HttpMessageConverterExtractor.java:92, vous devez continuer à déboguer, et dans mon cas, j'ai obtenu ceci:

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

et ça:

outputMessage.getBody().flush();

outputMessage.getBody () contient le message envoyé par http (type de message)


la journalisation des traces peut être trop verbeuse ... et s'il y a des milliers de requêtes par seconde ??
Gervasio Amy
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.