Spring RestTemplate: ¿cómo habilitar la depuración / registro completo de solicitudes / respuestas?

220

He estado usando Spring RestTemplate durante un tiempo y constantemente me encuentro con un muro cuando intento depurar sus solicitudes y respuestas. Básicamente, estoy buscando ver las mismas cosas que veo cuando uso curl con la opción "detallada" activada. Por ejemplo :

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

Mostraría tanto los datos enviados como los datos recibidos (incluidos los encabezados, las cookies, etc.).

He revisado algunas publicaciones relacionadas como: ¿Cómo registro la respuesta en Spring RestTemplate? pero no he logrado resolver este problema.

Una forma de hacer esto sería cambiar el código fuente de RestTemplate y agregar algunas declaraciones de registro adicionales allí, pero creo que este enfoque es realmente el último recurso. Debería haber alguna forma de decirle a Spring Web Client / RestTemplate que registre todo de una manera mucho más amigable.

Mi objetivo sería poder hacer esto con código como:

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

y luego obtener el mismo tipo de información de depuración (como obtengo con curl) en el archivo de registro o en la consola. Creo que esto sería extremadamente útil para cualquiera que use Spring RestTemplate y tenga problemas. Usar curl para depurar sus problemas de RestTemplate simplemente no funciona (en algunos casos).

Paul Sabou
fuente
30
Advertencia para cualquiera que lea en 2018: ¡No hay una respuesta simple para esto!
davidfrancis
3
La forma más fácil es usar un punto de interrupción en el método de escritura (...) de la clase AbstractHttpMessageConverter, hay un objeto outputMessage donde puede ver los datos. PD: puede copiar el valor y luego formatearlo con el formateador en línea.
Sergey Chepurnov el
1
Parece que esto debería ser fácil de hacer en primavera, pero, a juzgar por las respuestas aquí, no es el caso. Entonces, otra solución sería evitar Spring por completo y usar una herramienta como Fiddler para capturar la solicitud / respuesta.
michaelok
lea la respuesta a esta pregunta en el siguiente enlace: spring-resttemplate-how-to-enable-full-debugging-logging-of-peticiones-respuestas
Solanki Vaibhav
Julio de 2019: como todavía no hay una solución simple para esta pregunta, intenté dar un resumen de las otras 24 respuestas (hasta ahora) y sus comentarios y discusiones en mi propia respuesta a continuación . Espero eso ayude.
Chris

Respuestas:

206

Solo para completar el ejemplo con una implementación completa de ClientHttpRequestInterceptorrastrear solicitud y respuesta:

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

}

Luego instanciar RestTemplateusando a BufferingClientHttpRequestFactoryy el LoggingRequestInterceptor:

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

Se BufferingClientHttpRequestFactoryrequiere como queremos usar el cuerpo de respuesta tanto en el interceptor como para el código de llamada inicial. La implementación predeterminada permite leer el cuerpo de la respuesta solo una vez.

sofiene zaghdoudi
fuente
27
Esto está mal. Si lee la secuencia, el código de la aplicación no podrá leer la respuesta.
James Watkins
28
le hemos dado a RestTemplate un BufferingClientHttpRequestFactory para que podamos leer la respuesta dos veces.
sofiene zaghdoudi
16
Hemos estado usando esta técnica durante aproximadamente 3 meses. Solo funciona con RestTemplate configurado con un BufferingClientHttpResponseWrappercomo @sofienezaghdoudi implica. Sin embargo, no funciona cuando se usa en pruebas que usan el marco de trabajo mockServer de spring, ya que MockRestServiceServer.createServer(restTemplate)sobrescribe RequestFactory en InterceptingClientHttpRequestFactory.
RubesMN
8
La técnica es buena, la implementación es incorrecta. 404 case, response.getBody () throw IOException -> nunca se obtiene el registro y, lo que es peor, se convertirá en una ResourceAccessException en su código adicional, en lugar de una RestClientResponseException
MilacH
66
gracias por la respuesta. Pero es una mala práctica tener múltiples "log.debug", ya que podría extenderse a muchos otros registros. Es mejor usar una sola instrucción log.debug para estar seguro de que todo está en el mismo lugar
user2447161
127

en Spring Boot puede obtener la solicitud / respuesta completa configurando esto en propiedades (u otro método de 12 factores)

logging.level.org.apache.http=DEBUG

esto produce

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

y respuesta

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

o simplemente logging.level.org.apache.http.wire=DEBUGque parece contener toda la información relevante

xenoterracida
fuente
44
Esto fue lo más simple que hizo lo que quería. Le recomiendo incluir esto en la respuesta aceptada.
michaelavila
22
Según el 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 no utiliza estas clases de Apache de forma predeterminada, como lo señala @OrtomalaLokni, por lo que también debe incluir cómo usarlas además de cómo imprimir la depuración cuando se usan.
Captain Man
Me estoy poniendo así:http-outgoing-0 << "[0x1f][0x8b][0x8][0x0][0x0][0x0][0x0][0x0]
Partha Sarathi Ghosh
2
@ParthaSarathiGhosh El contenido probablemente esté codificado con gzip, por lo que no está viendo el texto sin formato.
Matthew Buckett el
80

Extendiendo la respuesta @hstoerr con algún código:


Crear LoggingRequestInterceptor para registrar respuestas de solicitudes

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

Configurar 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());
mjj1409
fuente
Esto no está disponible hasta la versión spring-3.1.
Gyan
3
no responde a la pregunta de 'respuesta de registro', sino que deja un comentario de // registro en su lugar.
Jiang YD
1
hacer el registro fue fácil, pero esto funciona solo para solicitudes, no veo cuerpos de respuesta, supongamos que tengo un objeto de respuesta, pero leer su flujo no es una buena idea.
Pavel Niedoba
11
@PavelNiedoba El BufferClientHttpRequestFactory permite que la respuesta se lea más de una vez.
mjj1409
2
Esto funciona bien si necesita almacenar información sobre la solicitud / respuesta en una base de datos para la depuración y el registro regular no se ajusta a sus necesidades.
GameSalutes
32

Su mejor opción es agregar logging.level.org.springframework.web.client.RestTemplate=DEBUGal application.propertiesarchivo.

Otras soluciones como la configuración log4j.logger.httpclient.wireno siempre funcionarán porque suponen que usa log4jApache HttpClient, lo que no siempre es cierto.

Sin embargo, tenga en cuenta que esta sintaxis solo funcionará en las últimas versiones de Spring Boot.

gamliela
fuente
55
Esto no registra el cuerpo de la solicitud y la respuesta, solo la url y el tipo de solicitud (spring-web-4.2.6)
dve
1
Tiene usted razón, no es un wireregistro, que sólo incluye información esencial como url, código resepone, etc. parámetros POST
gamliela
1
lo que realmente quieres es este stackoverflow.com/a/39109538/206466
xenoterracide el
Esto está bien, ¡pero no se pudo ver el cuerpo de respuesta!
sunleo
Brillante. Aunque no imprime el cuerpo de respuesta, sigue siendo muy útil. Gracias.
Chris
30

Ninguna de estas respuestas realmente resuelve el 100% del problema. mjj1409 obtiene la mayor parte, pero evita convenientemente el problema de registrar la respuesta, lo que requiere un poco más de trabajo. Paul Sabou proporciona una solución que parece realista, pero no proporciona suficientes detalles para implementar realmente (y no funcionó en absoluto para mí). Sofiene consiguió el registro pero con un problema crítico: ¡la respuesta ya no es legible porque el flujo de entrada ya se ha consumido!

Recomiendo usar un BufferingClientHttpResponseWrapper para ajustar el objeto de respuesta para permitir leer el cuerpo de la respuesta varias veces:

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

}

Esto no consumirá InputStream porque el cuerpo de respuesta se carga en la memoria y se puede leer varias veces. Si no tiene el BufferingClientHttpResponseWrapper en su classpath, puede encontrar la implementación simple aquí:

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

Para configurar RestTemplate:

LoggingRequestInterceptor loggingInterceptor = new LoggingRequestInterceptor();
restTemplate.getInterceptors().add(loggingInterceptor);
James Watkins
fuente
mismo, responseCopy.getBody () arroja IOexception en el caso de 404, por lo que nunca envía de vuelta a su código adicional la respuesta y la normalmente RestClientResponseException se convierte en ResourceAccessException
MilacH
1
Deberías comprobar status==200, antesresponseCopy.getBody()
Anand Rockzz
44
Pero es un paquete privado. ¿Puso su LoggingRequestInterceptor en el paquete 'org.springframework.http.client'?
zbstof
2
¿qué pasa asyncRestTemplate? Debería devolver un ListenableFuturemensaje cuando lo intercepte, lo que no es posible alterar BufferingClientHttpResponseWrapperen una devolución de llamada.
Ömer Faruk Almalı
@ ÖmerFarukAlmalı En ese caso, necesitará usar chain o transform según la versión de Guava que esté usando. Ver: stackoverflow.com/questions/8191891/…
James Watkins
29

La solución dada por xenoterracide para usar

logging.level.org.apache.http=DEBUG

es bueno, pero el problema es que, por defecto, Apache HttpComponents no se usa.

Para usar Apache HttpComponents agregue a su pom.xml

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

y configurar RestTemplatecon:

RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory());
Ortomala Lokni
fuente
La forma más fácil, solo agregaré que no funciona con MockRestServiceServer, ya que sobrescribe requestFactory.
zbstof
Funciona bien y sin problemas menos config!
sunleo
29

Puede usar spring-rest-template-logger para registrar el RestTemplatetráfico HTTP.

Agregue una dependencia a su proyecto Maven:

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

Luego personalice su de la RestTemplatesiguiente manera:

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

Asegúrese de que el registro de depuración esté habilitado en application.properties:

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

Ahora todo el tráfico HTTP de RestTemplate se registrará org.hobsoft.spring.resttemplatelogger.LoggingCustomizeren el nivel de depuración.

DESCARGO DE RESPONSABILIDAD: Escribí esta biblioteca.

Mark Hobson
fuente
¿Por qué esta respuesta es rechazada? Me ayudó. Gracias, @Mark Hobson.
Raffael Bechara Rameh
3
Me alegro de que haya ayudado a @RaffaelBecharaRameh. Inicialmente fue rechazado porque no incluí instrucciones del proyecto vinculado. ¡Siéntete libre de votar si lo encontraste útil!
Mark Hobson
¿Apoya a través de Gradle?
BlackHatSamurai
1
@BlackHatSamurai spring-rest-template-logger es un artefacto regular de Maven, por lo que debería funcionar bien con Gradle.
Mark Hobson
1
Hola @erhanasikoglu, de nada! Así es, puedes verlo en uso aquí: github.com/markhobson/spring-rest-template-logger/blob/master/…
Mark Hobson
26

Finalmente encontré una manera de hacer esto de la manera correcta. La mayor parte de la solución proviene de ¿Cómo configuro Spring y SLF4J para que pueda iniciar sesión?

Parece que hay dos cosas que deben hacerse:

  1. Agregue la siguiente línea en log4j.properties: log4j.logger.httpclient.wire=DEBUG
  2. Asegúrese de que spring no ignore su configuración de registro

El segundo problema ocurre principalmente en entornos de primavera donde se usa slf4j (como fue mi caso). Como tal, cuando se usa slf4j, asegúrese de que sucedan las siguientes dos cosas:

  1. No hay una biblioteca de registro de comunes en su classpath: esto se puede hacer agregando los descriptores de exclusión en su pom:

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
  2. El archivo log4j.properties se almacena en algún lugar del classpath donde spring puede encontrarlo / verlo. Si tiene problemas con esto, una solución de último recurso sería colocar el archivo log4j.properties en el paquete predeterminado (no es una buena práctica, pero solo para ver que las cosas funcionan como espera)

Paul Sabou
fuente
77
Esto no funciona para mí, hice las dos cosas. No entiendo por qué necesito poner log4j.properties cuando no se usa de todos modos en mi proyecto (verificado por mvn dependency: tree)
Pavel Niedoba
Esto tampoco funciona para mí. Incluso intenté configurar el registrador raíz en modo de depuración y todavía nada.
James Watkins
"httpclient.wire.content" y "httpclient.wire.header" son nombres de registrador del marco Axis2. Se pueden usar para registrar, por ejemplo, solicitudes SOAP en un proyecto Spring si se realizan con Axis2.
Lathspell
11
httpclient.wireen realidad es de la biblioteca Apache HttpComponents HttpClient (consulte hc.apache.org/httpcomponents-client-ga/logging.html ). Esta técnica solo funcionará si ha RestTemplateconfigurado el uso deHttpComponentsClientHttpRequestFactory
Scott Frederick
21

Registro RestTemplate

Opción 1. Abra el registro de depuración.

Configurar RestTemplate

  • Por defecto, RestTemplate se basa en las instalaciones estándar de JDK para establecer conexiones HTTP. Puede cambiar para usar una biblioteca HTTP diferente, como Apache HttpComponents

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

Configurar registro

  • application.yml

    registro: nivel: org.springframework.web.client.RestTemplate: DEBUG

Opción 2. Uso del interceptor

Respuesta del envoltorio

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

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

}

Configurar RestTemplate

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

Configurar registro

  • Comprobar el paquete de LoggingRestTemplate, por ejemplo en application.yml:

    logging: level: com.example.logging: DEBUG

Opción 3. Usar httpcomponent

Importar dependencia de componente http

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

Configurar RestTemplate

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

Configurar registro

  • Comprobar el paquete de LoggingRestTemplate, por ejemplo en application.yml:

    registro: nivel: org.apache.http: DEBUG

usuario2746033
fuente
Solo tenga en cuenta: si desea configurar TestRestTemplate, configure RestTemplateBuilder: @Bean public RestTemplateBuilder restTemplateBuilder () {return new RestTemplateBuilder (). AdditionalInterceptors (Collections.singletonList (new LoggingRestTemplate ())); }
kingoleg
Tenga en cuenta también que el nuevo InputStreamReader (responseWrapper.getBody (), StandardCharsets.UTF_8)); puede arrojar un error si el "otro extremo" devolvió un error. Es posible que desee colocarlo en un bloque de prueba.
PeterS
15

---- Julio 2019 ----

(usando Spring Boot)

Me sorprendió que Spring Boot, con toda su magia de Configuración Cero, no proporciona una manera fácil de inspeccionar o registrar un cuerpo de respuesta JSON simple con RestTemplate. Revisé las diferentes respuestas y comentarios proporcionados aquí, y comparto mi propia versión destilada de lo que (todavía) funciona y me parece una solución razonable, dadas las opciones actuales (estoy usando Spring Boot 2.1.6 con Gradle 4.4 )

1. Usando Fiddler como proxy HTTP

En realidad, esta es una solución bastante elegante, ya que evita todos los esfuerzos engorrosos de crear su propio interceptor o cambiar el cliente http subyacente a apache (ver más abajo).

Instalar y ejecutar Fiddler

y entonces

agregar -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888a sus opciones de VM

2. Usando Apache HttpClient

Agregue Apache HttpClient a sus dependencias Maven o Gradle.

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

Usar HttpComponentsClientHttpRequestFactorycomo RequestFactory para RestTemplate. La forma más sencilla de hacerlo sería:

RestTemplate restTemplate = new RestTemplate();

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Habilite DEBUG en su application.propertiesarchivo (si está usando Spring Boot)

logging.level.org.apache.http=DEBUG

Si está utilizando Spring Boot, deberá asegurarse de tener un marco de registro configurado, por ejemplo, mediante el uso de una dependencia spring-boot-starter que incluya spring-boot-starter-logging.

3. Use un interceptor

Te dejaré leer las propuestas, contrapropuestas y trucos en las otras respuestas y comentarios y decidir por ti mismo si quieres seguir ese camino.

4. URL de registro y estado de respuesta sin cuerpo

Aunque esto no cumple con los requisitos establecidos para registrar el cuerpo, es una forma rápida y sencilla de comenzar a registrar sus llamadas REST. Muestra la URL completa y el estado de respuesta.

Simplemente agregue la siguiente línea a su application.propertiesarchivo (suponiendo que esté utilizando Spring Boot, y suponiendo que está utilizando una dependencia de inicio de arranque de Spring que incluye spring-boot-starter-logging)

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

La salida se verá más o menos así:

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]
Chris
fuente
2
No. 4 es la forma más fácil de depurar.
Yubaraj
1
El número 2 funcionó para mí. Registra el cuerpo de solicitud. ¡Gracias!
caglar
1
Encontré el número 3 en una forma fácil de hacer esto cuando llegué a este problema.
Bill Naylor
12

Además del registro de HttpClient descrito en la otra respuesta , también puede introducir un ClientHttpRequestInterceptor que lee el cuerpo de la solicitud y la respuesta y lo registra. Es posible que desee hacer esto si otras cosas también usan el HttpClient, o si desea un formato de registro personalizado. Precaución: querrá darle a RestTemplate un BufferingClientHttpRequestFactory para que pueda leer la respuesta dos veces.

Hans-Peter Störr
fuente
12

Como se indicó en las otras respuestas, el cuerpo de la respuesta necesita un tratamiento especial para que pueda leerse repetidamente (por defecto, su contenido se consume en la primera lectura).

En lugar de utilizar el BufferingClientHttpRequestFactoryal configurar la solicitud, el propio interceptor puede ajustar la respuesta y asegurarse de que el contenido se conserve y pueda leerse repetidamente (tanto por el registrador como por el consumidor de la respuesta):

Mi interceptor, que

  • amortigua el cuerpo de respuesta usando un contenedor
  • registra de una manera más compacta
  • también registra el identificador del código de estado (por ejemplo, 201 Creado)
  • incluye un número de secuencia de solicitud que permite distinguir fácilmente las entradas de registro concurrentes de múltiples hilos

Código:

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

Configuración:

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

Ejemplo de salida de registro:

2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 >  Request: POST http://localhost:53969/payment/v4/private/payment-lists/10022/templates
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 >  Headers: {Accept=[application/json, application/json], Content-Type=[application/json;charset=UTF-8], Content-Length=[986]}
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 >  Body: 
{"idKey":null, ...}
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 <  Response: 200 OK 
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 <  Headers: {Content-Type=[application/json;charset=UTF-8], Transfer-Encoding=[chunked], Date=[Mon, 08 Oct 2018 08:58:53 GMT]}
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 <  Body: 
{ "idKey" : "10022", ...  }
Peter Walser
fuente
1
Este funciona con la versión Spring 2019 manteniendo el cuerpo intacto.
Udo celebrada el
1
Funciona en la primavera 2.1.10 :) Gracias
Moler
8

application.properties

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

application.yml

logging:
  level:  
    root: WARN
    org.springframework.web.client: DEBUG
Elton Sandré
fuente
8

Puede que esta no sea la forma correcta de hacerlo, pero creo que este es el enfoque más simple para imprimir solicitudes y respuestas sin llenar demasiado los registros.

Al agregar debajo de 2 líneas, application.properties registra todas las solicitudes y respuestas en la primera línea para registrar las solicitudes y en la segunda línea para registrar las respuestas.

logging.level.org.springframework.web.client.RestTemplate=DEBUG
logging.level.org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor=DEBUG
Viggi
fuente
El registro de respuestas no funciona para mí. Simplemente registra el código de estado. ¿Debería registrar la carga útil?
badera
La clase HttpEntityMethodProcessor (v5.1.8) no registra nada.
Chris
6

Suponiendo que RestTemplateesté configurado para usar HttpClient 4.x, puede leer la documentación de registro de HttpClient aquí . Los registradores son diferentes a los especificados en las otras respuestas.

La configuración de registro para HttpClient 3.x está disponible aquí .

Emerson Farrugia
fuente
4

Tantas respuestas aquí requieren cambios de codificación y clases personalizadas y realmente no es necesario. Obtenga un proxy de depuración como fiddler y configure su entorno java para usar el proxy en la línea de comando (-Dhttp.proxyHost y -Dhttp.proxyPort) luego ejecute fiddler y podrá ver las solicitudes y respuestas en su totalidad. También viene con muchas ventajas auxiliares, como la capacidad de modificar los resultados y las respuestas antes y después de enviarlos a ejecutar experimentos antes de comprometerse a modificar el servidor.

El último problema que puede surgir es que si debe usar HTTPS, deberá exportar el certificado SSL desde el violinista e importarlo en la clave del almacén de claves de Java (cacerts): la contraseña predeterminada del almacén de claves de Java suele ser "changeit".

Lee Burch
fuente
1
Esto funcionó para mí usando intellij y la instalación regular de fiddle. Edité Ejecutar configuración y configuré las opciones de VM en -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888.
JD
¡Gracias! Esta es una solución bastante elegante en comparación con escribir su propio Interceptor.
Chris
3

Para iniciar sesión en Logback con la ayuda de Apache HttpClient:

Necesita Apache HttpClient en classpath:

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

Configure su RestTemplatepara usar HttpClient:

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Para registrar solicitudes y respuestas, agregue al archivo de configuración Logback:

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

O para iniciar sesión aún más:

<logger name="org.apache.http" level="DEBUG"/>
holmis83
fuente
¿Qué archivo de configuración de logback?
G_V
1
@G_V logback.xml o logback-test.xml para pruebas.
holmis83
También funciona org.apache.http.wire=DEBUGen tu application.propertiesahora
G_V
@G_V si está utilizando Spring-Boot. Mi respuesta funciona sin arranque.
holmis83
2

El truco de configurar su RestTemplatecon un BufferingClientHttpRequestFactoryno funciona si está utilizando alguno ClientHttpRequestInterceptor, lo que hará si está intentando iniciar sesión a través de interceptores. Esto se debe a la forma en que funciona InterceptingHttpAccessor(qué RestTemplatesubclases).

Larga historia corta ... solo use esta clase en lugar de RestTemplate(tenga en cuenta que esto usa la API de registro SLF4J, edite según sea necesario):

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

Estoy de acuerdo en que es una tontería que se necesita tanto trabajo solo para hacer esto.

Archie
fuente
2

Además de la discusión anterior, esto solo representa escenarios felices. probablemente no podrá registrar la respuesta si se produce un error .

En este caso, más todos los casos anteriores, debe anular DefaultResponseErrorHandler y configurarlo como se muestra a continuación

restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());
usuario666
fuente
2

Curiosamente, ninguna de estas soluciones funciona ya que RestTemplate no parece devolver la respuesta en algunos errores 500x de clientes y servidores. En ese caso, también tendrá que registrarlos implementando ResponseErrorHandler de la siguiente manera. Aquí hay un borrador de código, pero entiendes el punto:

Puede configurar el mismo interceptor que el controlador de errores:

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

Y la intercepción implementa ambas 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);
    }
}
kisna
fuente
¿Qué pasa si el cuerpo es multipart / form-data, hay una manera fácil de filtrar datos binarios (contenido del archivo) del registro?
Lucas
1

Como señaló @MilacH, hay un error en la implementación. Si se devuelve un statusCode> 400, se lanza una IOException, ya que no se invoca el errorHandler, desde los interceptores. La excepción se puede ignorar y luego se vuelve a detectar en el método del controlador.

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

}
Tony Findeisen
fuente
0

La mejor solución ahora, solo agrega dependencia:

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

Contiene una clase LoggingRequestInterceptor que puede agregar de esa manera a su RestTemplate:

integre esta utilidad agregándola como un interceptor a un RestTemplate de resorte, de la siguiente manera:

restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build());

y agregue una implementación slf4j a su marco como log4j.

o use directamente "Zg2proRestTemplate" . La "mejor respuesta" de @PaulSabou lo parece, ya que httpclient y todas las bibliotecas apache.http no se cargan necesariamente cuando se utiliza un Spring RestTemplate.

Moses Meyer
fuente
¿Cuál es la versión lanzada?
popalka
la versión lanzada ahora es 0.2
Moses Meyer
1
la facilidad de uso es excelente, pero carece de encabezados
WrRaThY
Además: todos los métodos útiles en LoggingRequestInterceptor son privados, lo cual es un problema cuando se trata de extensión (podría estar protegido)
WrRaThY
Lamentablemente, no puedo editar los comentarios después de 5 minutos. Todo lo que tiene que hacer para registrar encabezados es esto: log("Headers: {}", request.headers)adentro LoggingRequestInterceptor:traceRequesty log("Headers: {}", response.headers)adentro LoggingRequestInterceptor:logResponse. Es posible que desee pensar en agregar algunas banderas para registrar encabezados y cuerpo. Además, es posible que desee verificar el tipo de contenido del cuerpo para el registro (por ejemplo, registrar solo la aplicación / json *). Esto también debería ser configurable. en general, con esos pequeños ajustes tendrás una buena biblioteca para difundir. buen trabajo :)
WrRaThY
0

Quería agregar mi implementación de esto también. Pido disculpas por todos los puntos y coma que faltan, esto está escrito en Groovy.

Necesitaba algo más configurable que la respuesta aceptada proporcionada. Aquí hay un bean de plantilla de descanso que es muy ágil y registrará todo lo que el OP está buscando.

Clase de interceptor de registro personalizado:

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

Definición de frijol de plantilla de descanso:

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

Implementación:

@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
    }
}
Jason Slobotski
fuente
0

org.apache.http.wire proporciona registros demasiado ilegibles, por lo que uso el libro de registro para registrar la aplicación Servlet y RestTemplate req / resp para registrar

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

}
}
analizador
fuente
-1

En relación con la respuesta usando ClientHttpInterceptor, encontré una forma de mantener toda la respuesta sin las fábricas de Buffering. Simplemente almacene la secuencia de entrada del cuerpo de respuesta dentro de la matriz de bytes utilizando algún método de utilidades que copiará esa matriz del cuerpo, pero importante, rodee este método con try catch porque se romperá si la respuesta está vacía (esa es la causa de la excepción de acceso a recursos) y en catch solo cree una matriz de bytes vacía, y luego simplemente cree una clase interna anónima de ClientHttpResponse usando esa matriz y otros parámetros de la respuesta original. Luego, puede devolver ese nuevo objeto ClientHttpResponse a la cadena de ejecución de la plantilla del resto y puede registrar la respuesta utilizando la matriz de bytes del cuerpo que se almacenó previamente. De esta forma evitará consumir InputStream en la respuesta real y puede usar la respuesta de la plantilla de descanso tal como está. Nota,

NenadTzar
fuente
-2

mi configuración de registrador usó xml

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

entonces obtendrás algo como a continuación:

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]

a través de HttpMessageConverterExtractor.java:92, necesita continuar depurando, y en mi caso, obtuve esto:

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

y esto:

outputMessage.getBody().flush();

outputMessage.getBody () contiene el mensaje que envía http (tipo de publicación)

danshijin
fuente
el registro de rastreo puede ser demasiado detallado ... ¿y si hay miles de solicitudes por segundo?
Gervasio Amy