Spring RestTemplate - como habilitar a depuração / registro completo de solicitações / respostas?

220

Uso o Spring RestTemplate há algum tempo e sempre atendo em uma parede quando estou tentando depurar suas solicitações e respostas. Basicamente, estou olhando para ver as mesmas coisas que vejo quando uso o curl com a opção "detalhado" ativada. Por exemplo :

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

Exibe os dados enviados e os dados recebidos (incluindo os cabeçalhos, cookies, etc.).

Eu verifiquei algumas postagens relacionadas, como: Como faço para registrar a resposta no Spring RestTemplate? mas não consegui resolver esse problema.

Uma maneira de fazer isso seria realmente alterar o código-fonte RestTemplate e adicionar algumas instruções de log extras lá, mas eu consideraria essa abordagem realmente um último recurso. Deve haver uma maneira de dizer ao Spring Web Client / RestTemplate para registrar tudo de uma maneira muito mais amigável.

Meu objetivo seria ser capaz de fazer isso com código como:

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

e, em seguida, para obter o mesmo tipo de informação de depuração (como recebo com curl) no arquivo de log ou no console. Acredito que isso seria extremamente útil para quem usa o Spring RestTemplate e tem problemas. Usar o curl para depurar os problemas do RestTemplate simplesmente não funciona (em alguns casos).

Paul Sabou
fonte
30
Aviso para quem lê em 2018: Não há uma resposta simples para isso!
Davidfrancis
3
A maneira mais fácil é usar um método de ponto de interrupção na gravação (...) da classe AbstractHttpMessageConverter, existe um objeto outputMessage onde você pode ver os dados. PS Você pode copiar o valor e formatá-lo com o formatador online.
Sergey Chepurnov
1
Parece que isso deve ser fácil na primavera, mas, a julgar pelas respostas aqui - não é o caso. Portanto, outra solução seria ignorar completamente o Spring e usar uma ferramenta como o Fiddler para capturar a solicitação / resposta.
22619 michaelok
leia a resposta a esta questão a partir do seguinte link: primavera-resttemplate-how-to-enable-full-depuração-logging-de-pedidos-respostas
Solanki Vaibhav
Julho de 2019: Como ainda não há uma solução simples para esta pergunta, tentei fazer um resumo das outras 24 respostas (até agora) e seus comentários e discussões em minha própria resposta abaixo . Espero que ajude.
29419 Chris

Respostas:

206

Apenas para concluir o exemplo com uma implementação completa de ClientHttpRequestInterceptorpara rastrear solicitação e resposta:

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

}

Instancie RestTemplateusando ae BufferingClientHttpRequestFactoryo LoggingRequestInterceptor:

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

Isso BufferingClientHttpRequestFactoryé necessário, pois queremos usar o corpo da resposta no interceptador e no código de chamada inicial. A implementação padrão permite ler o corpo da resposta apenas uma vez.

sofiene zaghdoudi
fonte
27
Isto está errado. Se você ler o fluxo, o código do aplicativo não poderá ler a resposta.
James Watkins
28
demos ao RestTemplate um BufferingClientHttpRequestFactory para que possamos ler a resposta duas vezes.
Sofiene zaghdoudi
16
Estamos usando essa técnica há cerca de 3 meses. Funciona apenas com o RestTemplate configurado com o BufferingClientHttpResponseWrappersignificado de @sofienezaghdoudi. No entanto, ele não funciona quando usado em testes usando a estrutura mockServer do spring, pois MockRestServiceServer.createServer(restTemplate)substitui o RequestFactory em InterceptingClientHttpRequestFactory.
RubesMN
8
A técnica é boa, a implementação está errada. 404 caso, response.getBody () jogue IOException -> você nunca obter o registro para fora e até mesmo pior, ele se tornará um ResourceAccessException em seu código ainda mais, em vez de um RestClientResponseException
MILACH
5
Obrigado pela resposta. Mas é uma prática ruim ter vários "log.debug", pois eles podem se espalhar por muitos outros logs. É melhor usar uma única instrução log.debug assim que tiver certeza que tudo está no mesmo lugar
user2447161
127

no Spring Boot, você pode obter a solicitação / resposta completa, definindo-o nas propriedades (ou outro método de 12 fatores)

logging.level.org.apache.http=DEBUG

isso gera

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

e resposta

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

ou apenas o logging.level.org.apache.http.wire=DEBUGque parece conter todas as informações relevantes

xenoterracida
fonte
4
Essa foi a coisa mais simples que fiz o que eu queria. É altamente recomendável incluir isso na resposta aceita.
Michaelavila
22
De acordo com o javadoc do 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 1/17/17
22
O RestTemplate não usa essas classes Apache como padrão, como apontado por @OrtomalaLokni, portanto, você também deve incluir como usá-las, além de como imprimir a depuração quando elas estiverem sendo usadas.
Captain Man
Estou ficando assim:http-outgoing-0 << "[0x1f][0x8b][0x8][0x0][0x0][0x0][0x0][0x0]
Partha Sarathi Ghosh
2
@ParthaSarathiGhosh O conteúdo provavelmente está codificado em gzip e é por isso que você não está vendo o texto bruto.
Matthew Buckett
80

Estendendo a resposta @hstoerr com algum código:


Crie LoggingRequestInterceptor para registrar respostas de solicitações

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

Configuração 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
fonte
Isso não está disponível até a versão spring-3.1.
Gyan
3
ele não responde à pergunta de 'resposta de log', mas deixa um // faça um comentário de log.
Jiang YD
1
fazer o log foi fácil, mas isso funciona apenas para solicitações, não vejo corpos de resposta, suponha que eu tenha um objeto de resposta, mas ler seu fluxo não é uma boa ideia.
Pavel Niedoba
11
@PavelNiedoba O BufferClientHttpRequestFactory permite que a resposta seja lida mais de uma vez.
Mjj1409
2
Isso funciona bem se você precisar armazenar informações sobre solicitação / resposta em um banco de dados para depuração e o registro regular não atenda às suas necessidades.
GameSalutes 30/01
32

Sua melhor aposta é adicionar logging.level.org.springframework.web.client.RestTemplate=DEBUGao application.propertiesarquivo.

Outras soluções, como a configuração log4j.logger.httpclient.wire, nem sempre funcionam porque elas assumem o uso do log4jApache HttpClient, o que nem sempre é verdade.

Observe, no entanto, que essa sintaxe funcionará apenas nas versões mais recentes do Spring Boot.

gamliela
fonte
5
Isto não é de registrar o pedido e resposta de corpo, apenas a url e pedido Tipo (primavera-web-4.2.6)
dve
1
Você está certo, não é uma wiremadeireira, só inclui informações essenciais, como url, código resepone, parâmetros POST etc.
gamliela
1
o que você realmente quer é este stackoverflow.com/a/39109538/206466
xenoterracide
Isso é bom, mas o corpo da resposta não pôde ser visto!
sunleo
Brilhante. Embora não imprima o corpo da resposta, ainda é muito útil. Obrigado.
28419 Chris
30

Nenhuma dessas respostas realmente resolve 100% do problema. O mjj1409 obtém a maior parte, mas evita convenientemente o problema de registrar a resposta, o que exige um pouco mais de trabalho. Paul Sabou fornece uma solução que parece realista, mas não fornece detalhes suficientes para realmente implementar (e não funcionou para mim). Sofiene obteve o log, mas com um problema crítico: a resposta não é mais legível porque o fluxo de entrada já foi consumido!

Eu recomendo usar um BufferingClientHttpResponseWrapper para agrupar o objeto de resposta para permitir a leitura do corpo da resposta várias vezes:

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

}

Isso não consumirá o InputStream porque o corpo da resposta é carregado na memória e pode ser lido várias vezes. Se você não possui o BufferingClientHttpResponseWrapper em seu caminho de classe, poderá encontrar a implementação simples aqui:

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

Para configurar o RestTemplate:

LoggingRequestInterceptor loggingInterceptor = new LoggingRequestInterceptor();
restTemplate.getInterceptors().add(loggingInterceptor);
James Watkins
fonte
same, responseCopy.getBody () lança IOexception no caso de 404, para que você nunca envie de volta para o seu código adicional a resposta e a normalmente RestClientResponseException se torne uma ResourceAccessException
MilacH
1
Você deve verificar status==200antesresponseCopy.getBody()
Anand Rockzz
4
Mas é pacote privado. Você colocou seu LoggingRequestInterceptor no pacote 'org.springframework.http.client'?
zbstof
2
que tal asyncRestTemplate? Seria necessário retornar um ListenableFuturequando você o interceptar, o que não é possível alterar BufferingClientHttpResponseWrapperem um retorno de chamada.
Ömer Faruk Almalı
@ ÖmerFarukAlmalı Nesse caso, você precisará usar cadeia ou transformação, dependendo da versão do Guava que estiver usando. Veja: stackoverflow.com/questions/8191891/…
James Watkins
29

A solução dada pelo xenoterracida para usar

logging.level.org.apache.http=DEBUG

é bom, mas o problema é que, por padrão, o Apache HttpComponents não é usado.

Para usar o Apache HttpComponents, adicione ao seu pom.xml

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

e configure RestTemplatecom:

RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory());
Ortomala Lokni
fonte
A maneira mais fácil, acrescentarei apenas que ele não funciona com MockRestServiceServer, pois substitui requestFactory.
zbstof
Trabalhando bem e sem problemas menos configuração!
sunleo
29

Você pode usar o spring-rest-template-logger para registrar o RestTemplatetráfego HTTP.

Adicione uma dependência ao seu projeto Maven:

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

Em seguida, personalize o seu da RestTemplateseguinte maneira:

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

Verifique se o log de depuração está ativado em application.properties:

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

Agora todo o tráfego HTTP RestTemplate será registrado org.hobsoft.spring.resttemplatelogger.LoggingCustomizerno nível de depuração.

AVISO LEGAL: Eu escrevi esta biblioteca.

Mark Hobson
fonte
Por que esta resposta é reduzida? Isso me ajudou. Obrigado, @Mark Hobson.
Raffael Bechara Rameh
3
Que bom que ajudou @RaffaelBecharaRameh. Foi inicialmente recusado porque não incorporei instruções do projeto vinculado. Sinta-se livre para votar se você achou útil!
Mark Hobson
Você suporta via Gradle?
BlackHatSamurai
1
O @BlackHatSamurai spring-rest-template-logger é um artefato regular do Maven, portanto deve funcionar bem com Gradle.
Mark Hobson
1
Olá @erhanasikoglu, de nada! É isso mesmo, você pode vê-lo em uso aqui: github.com/markhobson/spring-rest-template-logger/blob/master/…
Mark Hobson
26

Finalmente encontrei uma maneira de fazer isso da maneira certa. A maior parte da solução vem de Como eu configuro o Spring e o SLF4J para que eu possa obter o log?

Parece que há duas coisas que precisam ser feitas:

  1. Inclua a seguinte linha em log4j.properties: log4j.logger.httpclient.wire=DEBUG
  2. Verifique se o spring não ignora sua configuração de log

A segunda questão ocorre principalmente em ambientes de primavera em que o slf4j é usado (como foi o meu caso). Assim, quando slf4j é usado, verifique se as duas coisas a seguir acontecem:

  1. Não existe uma biblioteca de registro comum em seu caminho de classe: isso pode ser feito adicionando os descritores de exclusão em seu pom:

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
  2. O arquivo log4j.properties é armazenado em algum lugar do caminho de classe onde o Spring pode encontrá-lo / vê-lo. Se você tiver problemas com isso, uma solução de último recurso seria colocar o arquivo log4j.properties no pacote padrão (não é uma boa prática, mas apenas para ver se as coisas funcionam conforme o esperado)

Paul Sabou
fonte
7
Isso não funciona para mim, eu fiz as duas coisas. Eu não entendo por que eu preciso para colocar log4j.properties quando não é usado de qualquer maneira no meu projeto (marcada pela dependência mvn: árvore)
Pavel Niedoba
Isso também não funciona para mim. Eu até tentei definir o logger raiz para o modo Debug e ainda nada.
James Watkins
"httpclient.wire.content" e "httpclient.wire.header" são nomes de criadores de log da estrutura do Axis2. Eles podem ser usados ​​para registrar, por exemplo, solicitações SOAP em um projeto Spring se forem feitas usando o Axis2.
Lathspell # 10/17
11
httpclient.wireé realmente da biblioteca Apache HttpComponents HttpClient (consulte hc.apache.org/httpcomponents-client-ga/logging.html ). Essa técnica funcionará apenas se você tiver RestTemplateconfigurado para usar oHttpComponentsClientHttpRequestFactory
Scott Frederick
20

Log RestTemplate

Opção 1. Abra o log de depuração.

Configurar RestTemplate

  • Por padrão, o RestTemplate conta com os recursos padrão do JDK para estabelecer conexões HTTP. Você pode alternar para usar uma biblioteca HTTP diferente, como Apache HttpComponents

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

Configurar log

  • application.yml

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

Opção 2. Usando o Interceptor

Resposta do Wrapper

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.util.StreamUtils;

public final class BufferingClientHttpResponseWrapper implements ClientHttpResponse {

    private final ClientHttpResponse response;

    private byte[] body;


    BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
        this.response = response;
    }

    public HttpStatus getStatusCode() throws IOException {
        return this.response.getStatusCode();
    }

    public int getRawStatusCode() throws IOException {
        return this.response.getRawStatusCode();
    }

    public String getStatusText() throws IOException {
        return this.response.getStatusText();
    }

    public HttpHeaders getHeaders() {
        return this.response.getHeaders();
    }

    public InputStream getBody() throws IOException {
        if (this.body == null) {
            this.body = StreamUtils.copyToByteArray(this.response.getBody());
        }
        return new ByteArrayInputStream(this.body);
    }

    public void close() {
        this.response.close();
    }
}

Implementar o 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 log

  • Verifique o pacote do LoggingRestTemplate, por exemplo em application.yml:

    log: nível: com.example.logging: DEBUG

Opção 3. Usando httpcomponent

Importar dependência de httpcomponent

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

  • Verifique o pacote do LoggingRestTemplate, por exemplo em application.yml:

    log: level: org.apache.http: DEBUG

user2746033
fonte
Observe: se você deseja configurar TestRestTemplate, configure RestTemplateBuilder: @Bean public RestTemplateBuilder restTemplateBuilder () {retorne novo RestTemplateBuilder (). AdditionalInterceptors (Collections.singletonList (new LoggingRestTemplate ())); }
kingoleg 21/09/18
Observe também que o novo InputStreamReader (responseWrapper.getBody (), StandardCharsets.UTF_8)); pode gerar um erro se o "outro extremo" retornar um erro. Você pode colocá-lo em um bloco try.
9138 PeterS
15

---- julho de 2019 ----

(usando o Spring Boot)

Fiquei surpreso que o Spring Boot, com toda a mágica de Configuração Zero, não fornece uma maneira fácil de inspecionar ou registrar um corpo de resposta JSON simples com o RestTemplate. Examinei as várias respostas e comentários fornecidos aqui e estou compartilhando minha própria versão destilada do que (ainda) funciona e me parece uma solução razoável, dadas as opções atuais (estou usando o Spring Boot 2.1.6 com Gradle 4.4 )

1. Usando o Fiddler como proxy http

Essa é, na verdade, uma solução bastante elegante, pois ignora todos os esforços pesados ​​de criar seu próprio interceptor ou alterar o cliente http subjacente para apache (veja abaixo).

Instale e execute o Fiddler

e depois

adicione -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888às suas opções de VM

2. Usando o Apache HttpClient

Adicione o Apache HttpClient às suas dependências do Maven ou Gradle.

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

Use HttpComponentsClientHttpRequestFactorycomo RequestFactory para RestTemplate. A maneira mais simples de fazer isso seria:

RestTemplate restTemplate = new RestTemplate();

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Ative DEBUG no seu application.propertiesarquivo (se você estiver usando o Spring Boot)

logging.level.org.apache.http=DEBUG

Se você estiver usando o Spring Boot, precisará ter uma estrutura de registro configurada, por exemplo, usando uma dependência do spring-boot-starter que inclua spring-boot-starter-logging.

3. Use um interceptador

Vou deixar você ler as propostas, contrapropostas e dicas nas outras respostas e comentários e decidir por si mesmo se deseja seguir esse caminho.

4. URL do log e status da resposta sem corpo

Embora isso não atenda aos requisitos estabelecidos para registrar o corpo, é uma maneira rápida e simples de começar a registrar suas chamadas REST. Ele exibe o URL completo e o status da resposta.

Basta adicionar a seguinte linha ao seu application.propertiesarquivo (supondo que você esteja usando o Spring Boot e supondo que você esteja usando uma dependência do iniciador do boot que inclua spring-boot-starter-logging)

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

A saída será mais ou menos assim:

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
fonte
2
O número 4 é a maneira mais fácil de depurar.
Yubaraj 9/08/19
1
O número 2 funcionou para mim. Ele registra o corpo da solicitação. Obrigado!
Caglar 28/11/19
1
Eu achei o número 3 uma maneira fácil de fazer isso quando cheguei a esse problema.
Bill Naylor
12

Além do log do HttpClient descrito na outra resposta , você também pode introduzir um ClientHttpRequestInterceptor que lê o corpo da solicitação e a resposta e a registra. Você pode fazer isso se outras coisas também usarem o HttpClient ou se desejar um formato de log personalizado. Cuidado: você deseja fornecer ao RestTemplate um BufferingClientHttpRequestFactory, para poder ler a resposta duas vezes.

Hans-Peter Störr
fonte
12

Conforme indicado nas outras respostas, o corpo da resposta precisa de tratamento especial para que possa ser lido repetidamente (por padrão, seu conteúdo é consumido na primeira leitura).

Em vez de usar BufferingClientHttpRequestFactoryao configurar a solicitação, o próprio interceptador pode agrupar a resposta e garantir que o conteúdo seja retido e possa ser lido repetidamente (pelo criador de logs e pelo consumidor da resposta):

Meu interceptador, que

  • armazena em buffer o corpo da resposta usando um wrapper
  • registra de forma mais compacta
  • também registra o identificador do código de status (por exemplo, 201 criado)
  • inclui um número de sequência de solicitação que permite distinguir facilmente entradas de log simultâneas de vários threads

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

Configuração:

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

Exemplo de saída de log:

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
fonte
1
Este funciona com a versão Spring 2019 mantendo o corpo intacto.
Udo Held
1
Funciona na Primavera 2.1.10 :) Obrigado
Moler
8

application.properties

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

application.yml

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

Pode não ser a maneira correta de fazer isso, mas acho que essa é a abordagem mais simples para imprimir solicitações e respostas sem preencher muito os logs.

Adicionando abaixo de 2 linhas, application.properties registra todas as solicitações e respostas 1ª linha para registrar as solicitações e 2ª linha para registrar as respostas.

logging.level.org.springframework.web.client.RestTemplate=DEBUG
logging.level.org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor=DEBUG
Viggi
fonte
As respostas de log não funcionam para mim. Apenas registra o código de status. Ele deve registrar a carga útil?
badera
A classe HttpEntityMethodProcessor (v5.1.8) não registra nada.
31419 Chris
6

Supondo que RestTemplateesteja configurado para usar o HttpClient 4.x, você pode ler a documentação de log do HttpClient aqui . Os registradores são diferentes dos especificados nas outras respostas.

A configuração de log do HttpClient 3.x está disponível aqui .

Emerson Farrugia
fonte
4

Tantas respostas aqui exigem alterações de codificação e classes personalizadas e isso realmente não é necessário. Gte um proxy de depuração como o violinista e defina seu ambiente java para usá-lo na linha de comando (-Dhttp.proxyHost e -Dhttp.proxyPort) e execute o violinista e você poderá ver as solicitações e respostas na íntegra. Também possui muitas vantagens auxiliares, como a capacidade de mexer nos resultados e nas respostas antes e depois de serem enviadas para executar experimentos antes de se comprometer com a modificação do servidor.

O último problema que pode surgir é que se você deve usar HTTPS, precisará exportar o certificado SSL do violinista e importá-lo para a dica java keystore (cacerts): a senha padrão do keystore java é geralmente "changeit".

Lee Burch
fonte
1
Isso funcionou para mim usando intellij e a instalação regular do fiddle. Editei a configuração de execução e defina as opções da VM como -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888.
JD
Obrigado! Esta é uma solução bastante elegante em comparação com a criação do seu próprio Interceptor.
31419 Chris
3

Para fazer logon no Logback com a ajuda do Apache HttpClient:

Você precisa do Apache HttpClient no caminho de classe:

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

Configure o seu RestTemplatepara usar HttpClient:

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Para registrar solicitações e respostas, adicione ao arquivo de configuração do Logback:

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

Ou para registrar ainda mais:

<logger name="org.apache.http" level="DEBUG"/>
holmis83
fonte
Qual arquivo de configuração de logback?
G_V
1
@G_V logback.xml ou logback-test.xml para testes.
holmis83
Também funciona com o org.apache.http.wire=DEBUGseu application.propertiesagora
G_V
@G_V se você estiver usando o Spring-Boot. Minha resposta funciona sem o Boot.
holmis83
2

O truque de configurar o seu RestTemplatecom a BufferingClientHttpRequestFactorynão funciona se você estiver usando algum ClientHttpRequestInterceptor, o que você fará se estiver tentando registrar através de interceptadores. Isto é devido à maneira que InterceptingHttpAccessor(que RestTemplatesubclasses) funciona.

Para encurtar a história ... basta usar esta classe no lugar de RestTemplate(observe que ela usa a API de log do SLF4J, edite conforme necessário):

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

Concordo que é bobagem que é preciso muito trabalho apenas para fazer isso.

Archie
fonte
2

Adicionando à discussão acima, isso representa apenas cenários felizes. provavelmente você não poderá registrar a resposta se ocorrer um erro .

Nesse caso, além de todos os casos acima, você deve substituir DefaultResponseErrorHandler e configurá-lo como abaixo

restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());
user666
fonte
2

Estranhamente, nenhuma dessas soluções funciona, pois o RestTemplate parece não retornar a resposta em alguns erros 500x do cliente e do servidor. Nesse caso, você também os registrará implementando ResponseErrorHandler da seguinte maneira. Aqui está um rascunho do código, mas você entendeu:

Você pode definir o mesmo interceptador que o manipulador de erros:

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

E a interceptação implementa as duas 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
fonte
E se o corpo for multipart / form-data, existe uma maneira fácil de filtrar dados binários (conteúdo do arquivo) do log?
Luke
1

Como o @MilacH apontou, há um erro na implementação. Se um statusCode> 400 for retornado, uma IOException será lançada, pois o errorHandler não é chamado, dos interceptadores. A exceção pode ser ignorada e, em seguida, capturada novamente no método manipulador.

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
fonte
0

Melhor solução agora, basta adicionar dependência:

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

Ele contém uma classe LoggingRequestInterceptor que você pode adicionar dessa maneira ao seu RestTemplate:

integre esse utilitário adicionando-o como interceptador a um RestTemplate de mola, da seguinte maneira:

restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build());

e adicione uma implementação slf4j à sua estrutura, como log4j.

ou use diretamente "Zg2proRestTemplate" . A "melhor resposta" de @PaulSabou parece assim, pois o httpclient e todas as bibliotecas apache.http não são necessariamente carregadas ao usar um RestTemplate de primavera.

Moses Meyer
fonte
qual é a versão lançada?
Popalka
versão lançada agora é 0.2
Moses Meyer
1
facilidade de uso é grande, mas que carece de cabeçalhos
WrRaThY
Adicionalmente: todos os métodos úteis em LoggingRequestInterceptor são privados, o que é um problema quando se trata de extensão (poderia ser protegido)
WrRaThY
infelizmente, não consigo editar comentários depois de cinco minutos. Tudo o que você precisa fazer para registrar cabeçalhos é o seguinte: log("Headers: {}", request.headers)in LoggingRequestInterceptor:traceRequeste log("Headers: {}", response.headers)in LoggingRequestInterceptor:logResponse. Você pode pensar em adicionar alguns sinalizadores para registrar cabeçalhos e corpo. Além disso - convém verificar o tipo de conteúdo do corpo para registro (por exemplo, log only application / json *). Isso também deve ser configurável. Em suma, com esses pequenos ajustes, você terá uma boa biblioteca para espalhar. bom trabalho :)
WrRaThY
0

Queria adicionar minha implementação disso também. Peço desculpas por todos os pontos e vírgulas que faltam, isso está escrito em Groovy.

Eu precisava de algo mais configurável do que a resposta aceita fornecida. Aqui está um bean de modelo de descanso que é muito ágil e registrará tudo o que o OP está procurando.

Classe de interceptador de log 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=================================================")
        }
    }
}

Definição de Bean de modelo 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
}

Implementação:

@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
fonte
0

org.apache.http.wire fornece logs muito ilegíveis, então eu uso o logbook para registrar o aplicativo Servlet e 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;
    }

}
}
panser
fonte
-1

Em relação à resposta usando o ClientHttpInterceptor, encontrei uma maneira de manter toda a resposta sem as fábricas de buffer. Apenas armazene o fluxo de entrada do corpo da resposta na matriz de bytes usando algum método utils que copiará essa matriz do corpo, mas importante, envolva esse método com try catch, pois ele será interrompido se a resposta estiver vazia (que é a causa da Exceção de Acesso ao Recurso) e em catch, crie apenas uma matriz de bytes vazia e, em seguida, crie uma classe interna anônima de ClientHttpResponse usando essa matriz e outros parâmetros da resposta original. Depois, você pode retornar o novo objeto ClientHttpResponse para a cadeia de execução do modelo restante e registrar a resposta usando a matriz de bytes do corpo que foi armazenada anteriormente. Dessa forma, você evitará consumir InputStream na resposta real e poderá usar a resposta Rest Template como ela é. Nota,

NenadTzar
fonte
-2

minha configuração do logger usou xml

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

então você terá algo como abaixo:

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]

através de HttpMessageConverterExtractor.java:92, você precisa continuar depurando e, no meu caso, obtive o seguinte:

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

e isto:

outputMessage.getBody().flush();

outputMessage.getBody () contém a mensagem que o http (tipo de postagem) envia

danshijin
fonte
o log de rastreamento pode ser muito detalhado ... e se houver milhares de solicitações por segundo?
Gervasio Amy