J'utilise Spring RestTemplate depuis un certain temps et je me heurte régulièrement à un mur lorsque j'essaie de déboguer ses demandes et ses réponses. En gros, je cherche à voir les mêmes choses que 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é certaines publications telles que: Comment enregistrer la réponse dans Spring RestTemplate? mais je n’ai pas réussi à résoudre ce problème.
Une solution consiste à modifier le code source de RestTemplate et à y ajouter des instructions de journalisation supplémentaires, mais j’aimerais trouver cette approche vraiment une solution de dernier recours. Il devrait y avoir un moyen de dire à Spring Web Client/RestTemplate de tout consigner de manière beaucoup plus conviviale.
Mon objectif serait de pouvoir le faire avec un code tel que:
restTemplate.put("http://someurl", objectToPut, urlPathValues);
et ensuite pour obtenir le même type d'informations de débogage (comme je l'obtiens avec curl) dans le fichier journal ou dans la console ... Je pense que cela serait extrêmement utile pour quiconque utilise 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).
J'ai enfin trouvé un moyen de faire cela de la bonne manière ... La majorité de la solution vient de. /. Comment puis-je configurer Spring et SLF4J pour pouvoir me connecter?
Il semble qu'il y ait deux choses à faire:
log4j.logger.httpclient.wire=DEBUG
Le deuxième problème concerne principalement les environnements printaniers où slf4j est utilisé (comme c'était mon cas) . Ainsi, lorsque slf4j est utilisé, assurez-vous que les deux choses suivantes se produisent:
Il n'y a pas de bibliothèque commons-logging dans votre chemin de classe: ceci peut être fait en ajoutant les descripteurs d'exclusion dans votre pom:
<exclusions><exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
Le fichier log4j.properties est stocké quelque part dans le chemin de classe où spring peut le trouver/le voir. Si cela vous pose problème, une solution de dernier recours consisterait à placer le fichier log4j.properties dans le package par défaut (ce n'est pas une bonne pratique, mais juste de voir que tout fonctionne comme prévu)
Juste pour compléter l'exemple avec une implémentation complète de ClientHttpRequestInterceptor
pour suivre 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=================================================");
}
}
Puis instancier RestTemplate
en utilisant BufferingClientHttpRequestFactory
et le LoggingRequestInterceptor
:
RestTemplate restTemplate = new RestTemplate(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
List<ClientHttpRequestInterceptor> interceptors = new ArrayList<>();
interceptors.add(new LoggingRequestInterceptor());
restTemplate.setInterceptors(interceptors);
La BufferingClientHttpRequestFactory
est requise car nous souhaitons utiliser le corps de la 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 la réponse une seule fois.
Étendre la réponse @hstoerr avec du code:
Créer LoggingRequestInterceptor pour consigner 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
}
}
Configuration 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());
dans Spring Boot, vous pouvez obtenir la demande/réponse complète en la définissant dans les propriétés (ou une autre méthode à 12 facteurs)
logging.level.org.Apache.http=DEBUG
cette sortie
-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connecting to localhost/127.0.0.1:41827
-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connection established 127.0.0.1:39546<->127.0.0.1:41827
-DEBUG o.a.http.impl.execchain.MainClientExec : Executing request POST /v0/users HTTP/1.1
-DEBUG o.a.http.impl.execchain.MainClientExec : Target auth state: UNCHALLENGED
-DEBUG o.a.http.impl.execchain.MainClientExec : Proxy auth state: UNCHALLENGED
-DEBUG org.Apache.http.headers : http-outgoing-0 >> POST /v0/users HTTP/1.1
-DEBUG org.Apache.http.headers : http-outgoing-0 >> Content-Type: application/json;charset=UTF-8
-DEBUG org.Apache.http.headers : http-outgoing-0 >> Content-Length: 56
-DEBUG org.Apache.http.headers : http-outgoing-0 >> Host: localhost:41827
-DEBUG org.Apache.http.headers : http-outgoing-0 >> Connection: Keep-Alive
-DEBUG org.Apache.http.headers : http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)
-DEBUG org.Apache.http.headers : http-outgoing-0 >> Accept-Encoding: gzip,deflate
-DEBUG org.Apache.http.wire : http-outgoing-0 >> "POST /v0/users HTTP/1.1[\r][\n]"
-DEBUG org.Apache.http.wire : http-outgoing-0 >> "Content-Type: application/json;charset=UTF-8[\r][\n]"
-DEBUG org.Apache.http.wire : http-outgoing-0 >> "Content-Length: 56[\r][\n]"
-DEBUG org.Apache.http.wire : http-outgoing-0 >> "Host: localhost:41827[\r][\n]"
-DEBUG org.Apache.http.wire : http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
-DEBUG org.Apache.http.wire : http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)[\r][\n]"
-DEBUG org.Apache.http.wire : http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
-DEBUG org.Apache.http.wire : http-outgoing-0 >> "[\r][\n]"
-DEBUG org.Apache.http.wire : http-outgoing-0 >> "{"id":null,"email":"[email protected]","new":true}"
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":"[email protected]","new":true}"
ou juste logging.level.org.Apache.http.wire=DEBUG
qui semble contenir toutes les informations pertinentes
Aucune de ces réponses ne résout réellement 100% du problème. mjj1409 en tire l'essentiel, mais évite commodément le problème de la journalisation de la réponse, ce qui nécessite un peu plus de travail. Paul Sabou fournit une solution qui semble réaliste, mais ne fournit pas assez de détails pour réellement être mise en œuvre (et cela n'a pas du tout fonctionné pour moi). Sofiene a eu 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 BufferingClientHttpResponseWrapper pour envelopper l'objet de réponse afin de permettre la lecture du corps de la 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 BufferingClientHttpResponseWrapper sur votre chemin de classe, vous pouvez trouver l'implémentation simple ici:
Pour configurer le RestTemplate:
LoggingRequestInterceptor loggingInterceptor = new LoggingRequestInterceptor();
restTemplate.getInterceptors().add(loggingInterceptor);
La solution donnée par le xénoterracide à utiliser
logging.level.org.Apache.http=DEBUG
c'est bien, mais le problème est que par défaut, Apache HttpComponents n'est pas utilisé.
Pour utiliser Apache HttpComponents, ajoutez-le à votre pom.xml
<dependency>
<groupId>org.Apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
</dependency>
et configurer RestTemplate
avec:
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory());
Votre meilleur pari est d’ajouter logging.level.org.springframework.web.client.RestTemplate=DEBUG
au fichier application.properties
.
D'autres solutions telles que la définition de log4j.logger.httpclient.wire
ne fonctionneront pas toujours car elles supposent que vous utilisez log4j
et 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.
Par défaut, RestTemplate s'appuie sur les fonctionnalités standard du JDK pour établir des connexions HTTP. Vous pouvez passer à une autre bibliothèque HTTP telle que Apache HttpComponents.
@Bean Public RestTemplate restTemplate (constructeur RestTemplateBuilder) { RestTemplate restTemplate = builder.build (); retour restTemplate; }
application.yml
enregistrement: niveau: org.springframework.web.client.RestTemplate: DEBUG
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();
}
}
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;
}
}
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
RestTemplate restTemplate = builder.build();
restTemplate.setInterceptors(Collections.singletonList(new LoggingRestTemplate()));
return restTemplate;
}
Vérifiez le package de LoggingRestTemplate, par exemple dans application.yml
:
enregistrement: niveau: com.example.logging: DEBUG
<dependency>
<groupId>org.Apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
RestTemplate restTemplate = builder.build();
restTemplate.setRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory());
return restTemplate;
}
Vérifiez le package de LoggingRestTemplate, par exemple dans application.yml
:
enregistrement: niveau: org.Apache.http: DEBUG
Vous pouvez utiliser spring-rest-template-logger pour consigner le trafic HTTP à RestTemplate
.
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 RestTemplate
comme suit:
RestTemplate restTemplate = new RestTemplateBuilder()
.customizers(new LoggingCustomizer())
.build()
Désormais, tout le trafic HTTP RestTemplate sera consigné dans org.hobsoft.spring.resttemplatelogger.LoggingCustomizer
au niveau de débogage.
DISCLAIMER: J'ai écrit cette bibliothèque.
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 la consigne. Cela peut être utile si d'autres éléments utilisent également HttpClient ou si vous souhaitez un format de journalisation personnalisé. Attention: vous souhaiterez attribuer à RestTemplate une BufferingClientHttpRequestFactory permettant de lire la réponse deux fois.
En supposant que RestTemplate
est configuré pour utiliser HttpClient 4.x, vous pouvez lire la documentation de journalisation de HttpClient ici . Les enregistreurs sont différents de ceux spécifiés dans les autres réponses.
La configuration de la journalisation pour HttpClient 3.x est disponible ici .
Ce n'est peut-être pas la bonne façon de le faire, mais je pense que c'est l'approche la plus simple pour imprimer des demandes et des réponses sans trop en remplir dans les journaux.
En ajoutant ci-dessous 2 lignes, application.properties enregistre toutes les demandes et les réponses en 1ère ligne afin de consigner les demandes et la 2e ligne en consignation des réponses.
logging.level.org.springframework.web.client.RestTemplate=DEBUG
logging.level.org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor=DEBUG
J'ai été surpris de constater que Spring Boot, avec toute sa magie de configuration zéro, ne fournit 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 une solution raisonnable, compte tenu des options actuelles (j'utilise Spring Boot 2.1.6 avec Gradle 4.4). )
Il s’agit en fait d’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
et alors
ajoutez
-DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888
à votre VM Options
Ajoutez Apache HttpClient à vos dépendances Maven ou Gradle.
<dependency>
<groupId>org.Apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.9</version>
</dependency>
Utilisez HttpComponentsClientHttpRequestFactory
comme RequestFactory pour RestTemplate. Le moyen le plus simple de le faire serait:
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
Activer DEBUG dans votre fichier application.properties
(si vous utilisez Spring Boot)
logging.level.org.Apache.http=DEBUG
Si vous utilisez Spring Boot, vous devez vous assurer que vous avez un cadre de journalisation configuré, par exemple. en utilisant une dépendance printemps-démarrage-démarreur qui inclut spring-boot-starter-logging
.
Je vous laisse lire les propositions, les contre-propositions et les autres réponses et commentaires, puis décidez vous-même si vous souhaitez suivre cette voie.
Bien que cela ne réponde pas aux exigences énoncées pour la journalisation du corps, il s'agit d'un moyen simple et rapide de consigner vos appels REST. Il affiche l'URL complète et le statut de la réponse.
Ajoutez simplement la ligne suivante à votre fichier application.properties
(en supposant que vous utilisez Spring Boot, et en supposant que vous utilisez une dépendance de démarrage avec Spring Boot comprenant 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]
En ajoutant à la discussion ci-dessus, cela ne représente que des scénarios heureux. vous ne pourrez probablement pas enregistrer la réponse si un Erreur arrive.
Dans ce cas, plus tous les cas ci-dessus, vous devez remplacer DefaultResponseErrorHandler et le définir comme ci-dessous.
restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());
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é à la première lecture).
Au lieu d'utiliser la variable BufferingClientHttpRequestFactory
lors 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 qu'il peut être lu de manière répétée} (par le consignateur et par l'utilisateur de la réponse). :
Mon intercepteur qui
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 du 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", ... }
Étrangement, aucune de ces solutions ne fonctionne car RestTemplate ne semble pas renvoyer la réponse sur certaines erreurs client et serveur 500x. Dans ce cas, vous devrez également les consigner en implémentant ResponseErrorHandler comme suit. Voici un projet de code, mais vous comprenez 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);
}
}
L'astuce consistant à configurer votre RestTemplate
avec une BufferingClientHttpRequestFactory
ne fonctionne pas si vous utilisez une ClientHttpRequestInterceptor
, ce que vous ferez si vous essayez de vous connecter via des intercepteurs. Cela est dû à la façon dont fonctionne InterceptingHttpAccessor
(qui RestTemplate
sous-classes).
Long story story ... utilisez simplement cette classe à la place de RestTemplate
(notez que ceci utilise l'API de journalisation SLF4J, éditez-le 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 conviens que c’est idiot que cela prenne autant de travail que cela.
Tant de réponses ici nécessitent des modifications de code et des classes personnalisées et ce n'est vraiment pas nécessaire. Créez un proxy de débogage tel que fiddler et configurez votre environnement Java pour qu’il utilise le proxy sur la ligne de commande (-Dhttp.proxyHost et -Dhttp.proxyPort), puis exécutez fiddler et affichez les requêtes et les réponses dans leur intégralité. Fournit également de nombreux avantages accessoires, tels que la possibilité de modifier les résultats et les réponses avant et après leur envoi pour exécuter des expériences avant de s’engager à modifier le serveur.
Si vous devez utiliser HTTPS, vous devez enfin exporter le certificat SSL de fiddler et l'importer dans l'indice de magasin de clés Java (cacerts): le mot de passe par défaut du magasin de clés Java est généralement "changeit".
utilisation:
application.properties {
logging.level.org.springframework.web.client=DEBUG
}
ou YAML
application.yml {
logging:
level:
root: WARN
org.springframework.web.client: DEBUG
}
Référez-vous au Q/A pour enregistrer la demande et la réponse pour le modèle de repos en activant les lectures multiples sur HttpInputStream.
Pourquoi mon ClientHttpRequestInterceptor personnalisé avec une réponse vide
Comme @MilacH l'a souligné, il y a une erreur dans la mise en œuvre. Si un code de statut> 400 est renvoyé, une exception IOException est levée, car errorHandler n'est pas appelé à partir d'intercepteurs. L'exception peut être ignorée et est ensuite interceptée 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=================================================");
}
}
Je voulais ajouter ma mise en œuvre de cela aussi. Je m'excuse pour tous les points-virgules manquants, cela est écrit en Groovy.
J'avais besoin de quelque chose de plus configurable que la réponse acceptée fournie. Voici un haricot de gabarit de repos très agile qui enregistrera tout ce que le PO recherche.
Classe d'intercepteur 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 haricot de modèle de repos:
@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
}
}
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égrez cet utilitaire en l'ajoutant en tant qu'intercepteur à un RestTemplate de printemps, de la manière suivante:
restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build());
et ajoutez une implémentation slf4j à votre framework telle que log4j.
ou utilisez directement "Zg2proRestTemplate" . La "meilleure réponse" de @PaulSabou semble donc être la bonne, puisque httpclient et toutes les bibliothèques Apache.http ne sont pas nécessairement chargés lors de l'utilisation d'un RestTemplate à ressort.