Spring RestTemplate - jak włączyć pełne debugowanie / rejestrowanie żądań / odpowiedzi?


220

Używam Spring RestTemplate od jakiegoś czasu i konsekwentnie uderzam w ścianę, gdy próbuję debugować jego żądania i odpowiedzi. Zasadniczo chcę zobaczyć te same rzeczy, co widzę, gdy używam curl z włączoną opcją „verbose”. Na przykład :

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

Wyświetla zarówno przesłane dane, jak i otrzymane dane (w tym nagłówki, pliki cookie itp.).

Sprawdziłem kilka pokrewnych postów, takich jak: Jak zalogować odpowiedź w Spring RestTemplate? ale nie udało mi się rozwiązać tego problemu.

Jednym ze sposobów, aby to zrobić, jest zmiana kodu źródłowego RestTemplate i dodanie tam dodatkowych instrukcji logowania, ale uważam, że takie podejście jest naprawdę ostatecznością. Powinien istnieć sposób, aby poinformować Spring Web Client / RestTemplate, aby rejestrował wszystko w znacznie bardziej przyjazny sposób.

Moim celem byłoby, aby móc to zrobić za pomocą kodu takiego jak:

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

a następnie uzyskać ten sam typ informacji debugowania (jak w przypadku curl) w pliku dziennika lub w konsoli. Uważam, że byłoby to niezwykle przydatne dla każdego, kto używa Spring RestTemplate i ma problemy. Używanie curl do debugowania problemów z RestTemplate po prostu nie działa (w niektórych przypadkach).


31
Ostrzeżenie dla osób czytających w 2018 r .: Nie ma na to prostej odpowiedzi!
davidfrancis

3
Najprostszym sposobem jest użycie punktu przerwania w metodzie zapisu (...) klasy AbstractHttpMessageConverter, istnieje obiekt outputMessage, w którym można zobaczyć dane. PS Możesz skopiować wartość, a następnie sformatować ją za pomocą formatyzatora online.
Sergey Chepurnov

1
wydaje się, że powinno to być łatwe na wiosnę, ale sądząc po odpowiedziach tutaj - nie w tym przypadku. Innym rozwiązaniem byłoby całkowite obejście Springa i użycie narzędzia takiego jak Fiddler do przechwycenia żądania / odpowiedzi.
michaelok


Lipiec 2019: Ponieważ nadal nie ma prostego rozwiązania tego pytania, starałem się streścić pozostałe 24 odpowiedzi (jak dotąd) oraz ich komentarze i dyskusje we własnej odpowiedzi poniżej . Mam nadzieję, że to pomoże.
Chris

Odpowiedzi:


207

Wystarczy uzupełnić przykład pełną implementacją ClientHttpRequestInterceptorśledzenia żądania i odpowiedzi:

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

}

Następnie utwórz instancję RestTemplateza pomocą a BufferingClientHttpRequestFactoryi LoggingRequestInterceptor:

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

Jest BufferingClientHttpRequestFactoryto wymagane, ponieważ chcemy użyć treści odpowiedzi zarówno w przechwytywaczu, jak i w początkowym kodzie wywoławczym. Domyślna implementacja pozwala odczytać treść odpowiedzi tylko raz.


27
To jest źle. Jeśli czytasz strumień, kod aplikacji nie będzie w stanie odczytać odpowiedzi.
James Watkins

28
nadaliśmy RestTemplate BufferingClientHttpRequestFactory, abyśmy mogli przeczytać odpowiedź dwukrotnie.
sofiene zaghdoudi

16
Używamy tej techniki od około 3 miesięcy. Działa tylko z RestTemplate skonfigurowanym za pomocą BufferingClientHttpResponseWrapper@sofienezaghdoudi. Jednak nie działa, gdy jest używany w testach z wykorzystaniem wiosennego środowiska mockServer, ponieważ MockRestServiceServer.createServer(restTemplate)zastępuje obiekt RequestFactory InterceptingClientHttpRequestFactory.
RubesMN

8
Technika jest dobra, wdrożenie jest nieprawidłowe. 404 przypadek, response.getBody () wyrzuć wyjątek IOException -> nigdy nie dostaniesz dziennika dla wyjścia, a nawet najgorsze, stanie się ResourceAccessException w twoim dalszym kodzie, zamiast RestClientResponseException
MilacH

6
Dziękuję za odpowiedź. Ale jest to zła praktyka, aby mieć wiele „log.debug”, ponieważ można je rozłożyć na wiele innych dzienników. Lepiej jest użyć pojedynczej instrukcji log.debug, aby mieć pewność, że wszystko jest w tym samym miejscu
user2447161

129

w Spring Boot można uzyskać pełne żądanie / odpowiedź, ustawiając to we właściwościach (lub innej 12-czynnikowej metodzie)

logging.level.org.apache.http=DEBUG

to wychodzi

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

i odpowiedź

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

lub po prostu, logging.level.org.apache.http.wire=DEBUGktóre wydają się zawierać wszystkie istotne informacje


4
To była najprostsza rzecz, która zrobiła to, co chciałem. Gorąco zachęcam do włączenia tego do zaakceptowanej odpowiedzi.
michaelavila

22
Według javadoc 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 nie korzysta z tych klas Apache jako domyślnych, jak wskazał @OrtomalaLokni, więc powinieneś również dołączyć, jak ich używać, a także jak wydrukować debugowanie, gdy są używane.
Captain Man

Robię się tak:http-outgoing-0 << "[0x1f][0x8b][0x8][0x0][0x0][0x0][0x0][0x0]
Partha Sarathi Ghosh

2
@ParthaSarathiGhosh Zawartość jest prawdopodobnie zakodowana w formacie gzip, dlatego nie widzisz surowego tekstu.
Matthew Buckett

79

Rozszerzenie odpowiedzi @hstoerr o kod:


Utwórz LoggingRequestInterceptor, aby rejestrować odpowiedzi na żądania

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

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

Nie jest to dostępne do wersji wiosennej 3.1.
Gyan

3
nie odpowiada na pytanie „rejestrowanie odpowiedzi”, ale zamiast tego pozostaw komentarz // do rejestrowania.
Jiang YD

1
rejestrowanie było łatwe, ale działa to tylko w przypadku żądań, nie widzę treści odpowiedzi, załóżmy, że mam obiekt odpowiedzi, ale czytanie jego strumienia nie jest dobrym pomysłem.
Pavel Niedoba

11
@PavelNiedoba BufferClientHttpRequestFactory umożliwia odczyt odpowiedzi więcej niż jeden raz.
mjj1409

2
Działa to dobrze, jeśli chcesz przechowywać informacje o żądaniu / odpowiedzi w bazie danych w celu debugowania, a regularne rejestrowanie nie odpowiada Twoim potrzebom.
GameSalutes,

32

Najlepszym rozwiązaniem jest dodanie logging.level.org.springframework.web.client.RestTemplate=DEBUGdo application.propertiespliku.

Inne rozwiązania, takie jak ustawienie log4j.logger.httpclient.wire, nie zawsze będą działać, ponieważ zakładają, że używasz log4ji Apache HttpClient, co nie zawsze jest prawdą.

Należy jednak pamiętać, że ta składnia będzie działać tylko w najnowszych wersjach Spring Boot.


5
To nie jest rejestrowanie treści żądania i odpowiedzi, tylko adres URL i typ zapytania (spring-web-4.2.6)
dve

1
Masz rację, to nie jest wirerejestrowanie, zawiera tylko niezbędne informacje, takie jak adres URL, kod odesłany, parametry POST itp.
gamliela,


To dobrze, ale nie można było zobaczyć odpowiedzi!
sunleo

Znakomity. Chociaż nie drukuje treści odpowiedzi, nadal jest bardzo przydatny. Dziękuję Ci.
Chris

30

Żadna z tych odpowiedzi nie rozwiązuje w 100% problemu. mjj1409 otrzymuje większość, ale wygodnie unika się problemu z rejestrowaniem odpowiedzi, co zajmuje nieco więcej pracy. Paul Sabou zapewnia rozwiązanie, które wydaje się realistyczne, ale nie zawiera wystarczających szczegółów do faktycznego wdrożenia (i dla mnie to w ogóle nie zadziałało). Sofiene dostała rejestrację, ale z krytycznym problemem: odpowiedź nie jest już czytelna, ponieważ strumień wejściowy został już wykorzystany!

Polecam użycie BufferingClientHttpResponseWrapper do zawinięcia obiektu odpowiedzi, aby umożliwić wielokrotne czytanie treści odpowiedzi:

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

}

Nie spowoduje to zużycia InputStream, ponieważ treść odpowiedzi jest ładowana do pamięci i może być odczytywana wiele razy. Jeśli nie masz BufferingClientHttpResponseWrapper na ścieżce klasy, możesz znaleźć prostą implementację tutaj:

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

Aby skonfigurować RestTemplate:

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

to samo, responseCopy.getBody () wyrzuca IOexception w przypadku 404, więc nigdy nie odsyłasz odpowiedzi do dalszego kodu, a normalnie RestClientResponseException staje się ResourceAccessException
MilacH

1
Powinieneś sprawdzić status==200wcześniejresponseCopy.getBody()
Anand Rockzz

4
Ale to pakiet prywatny. Czy umieściłeś swój LoggingRequestInterceptor w pakiecie „org.springframework.http.client”?
zbstof

2
co asyncRestTemplate? Wymagane będzie zwrócenie znaku, ListenableFuturegdy go przechwycisz, czego nie można zmienić BufferingClientHttpResponseWrapperw wywołaniu zwrotnym.
Ömer Faruk Almalı

@ ÖmerFarukAlmalı W takim przypadku konieczne będzie użycie łańcucha lub transformacji w zależności od używanej wersji Guawy. Zobacz: stackoverflow.com/questions/8191891/…
James Watkins,

30

Do rejestrowania ruchu HTTP można użyć narzędzia Spring-rest-template-loggerRestTemplate .

Dodaj zależność do swojego projektu Maven:

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

Następnie dostosuj RestTemplatew następujący sposób:

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

Upewnij się, że rejestrowanie debugowania jest włączone w application.properties:

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

Teraz cały ruch HTTP RestTemplate zostanie zalogowany org.hobsoft.spring.resttemplatelogger.LoggingCustomizer na poziomie debugowania.

ZASTRZEŻENIE: Napisałem tę bibliotekę.


Dlaczego ta odpowiedź jest odrzucona? Pomogło mi to. Dzięki, Mark Hobson.
Raffael Bechara Rameh

3
Cieszę się, że pomogło @RaffaelBecharaRameh. Początkowo był oceniany, ponieważ nie osadziłem instrukcji z połączonego projektu. Zachęcamy do głosowania, jeśli okaże się to przydatne!
Mark Hobson

Czy wspierasz przez Gradle?
BlackHatSamurai

1
@BlackHatSamurai spring-rest-template-logger to zwykły artefakt Maven, więc powinien działać dobrze z Gradle.
Mark Hobson

1
Cześć @erhanasikoglu, nie ma za co! Zgadza się, możesz to zobaczyć tutaj: github.com/markhobson/spring-rest-template-logger/blob/master/...
Mark Hobson

29

Rozwiązanie podane przez ksenoterracid do użycia

logging.level.org.apache.http=DEBUG

jest dobry, ale problem polega na tym, że domyślnie Apache HttpComponents nie jest używany.

Aby użyć Apache HttpComponents, dodaj do pliku pom.xml

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

i skonfiguruj za RestTemplatepomocą:

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

Najprościej dodam tylko, że nie działa z MockRestServiceServer, ponieważ zastępuje requestFactory.
zbstof

Działa dobrze i bez problemów mniej konfiguracji!
sunleo

26

W końcu znalazłem sposób na zrobienie tego we właściwy sposób. Większość rozwiązań pochodzi z Jak skonfigurować Spring i SLF4J, aby uzyskać logowanie?

Wydaje się, że należy zrobić dwie rzeczy:

  1. Dodaj następujący wiersz w pliku log4j.properties: log4j.logger.httpclient.wire=DEBUG
  2. Upewnij się, że wiosna nie ignoruje konfiguracji logowania

Drugi problem dotyczy głównie środowisk wiosennych, w których używany jest slf4j (tak jak w moim przypadku). Jako taki, gdy używany jest slf4j, upewnij się, że zachodzą następujące dwie rzeczy:

  1. W ścieżce klasy nie ma biblioteki rejestrowania wspólnych: można to zrobić, dodając deskryptory wykluczeń w pom:

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
  2. Plik log4j.properties jest przechowywany gdzieś w ścieżce klasy, gdzie wiosna może go znaleźć / zobaczyć. Jeśli masz z tym problemy, rozwiązaniem ostatecznym byłoby umieszczenie pliku log4j.properties w domyślnym pakiecie (nie jest to dobra praktyka, ale po prostu sprawdza, czy wszystko działa zgodnie z oczekiwaniami)


7
To nie działa dla mnie, zrobiłem obie rzeczy. Nie rozumiem, dlaczego muszę umieścić plik log4j.properties, jeśli i tak nie jest używany w moim projekcie (sprawdzane przez zależność mvn: drzewo)
Pavel Niedoba

To mi też nie działa. Próbowałem nawet ustawić program rejestrujący root na tryb debugowania i nadal nic.
James Watkins

„httpclient.wire.content” i „httpclient.wire.header” to nazwy rejestratorów z frameworku Axis2. Można ich używać do rejestrowania np . Żądań SOAP w projekcie Spring, jeśli są one wykonywane przy użyciu Axis2.
lathspell

11
httpclient.wirepochodzi z biblioteki Apache HttpComponents HttpClient (patrz hc.apache.org/httpcomponents-client-ga/logging.html ). Ta technika zadziała tylko, jeśli RestTemplateskonfigurowałeśHttpComponentsClientHttpRequestFactory
Scott Frederick

22

Rejestrowanie RestTemplate

Opcja 1. Otwórz rejestrowanie debugowania.

Skonfiguruj RestTemplate

  • Domyślnie RestTemplate opiera się na standardowych funkcjach JDK do nawiązywania połączeń HTTP. Możesz przełączyć się na używanie innej biblioteki HTTP, takiej jak Apache HttpComponents

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

Skonfiguruj rejestrowanie

  • application.yml

    logowanie: poziom: org.springframework.web.client.RestTemplate: DEBUG

Opcja 2. Korzystanie z przechwytywacza

Odpowiedź opakowania

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

Wdrożenie przechwytywacza

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

}

Skonfiguruj RestTemplate

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

Skonfiguruj rejestrowanie

  • Sprawdź pakiet LoggingRestTemplate, na przykład w application.yml:

    logowanie: poziom: com.example.logging: DEBUG

Opcja 3. Korzystanie z komponentu http

Importuj zależność httpomponent

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

Skonfiguruj RestTemplate

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

Skonfiguruj rejestrowanie

  • Sprawdź pakiet LoggingRestTemplate, na przykład w application.yml:

    logowanie: poziom: org.apache.http: DEBUG


Uwaga: jeśli chcesz skonfigurować TestRestTemplate, skonfiguruj RestTemplateBuilder: @Bean public RestTemplateBuilder restTemplateBuilder () {return new RestTemplateBuilder (). AdditionalInterceptors (Collections.singletonList (new LoggingRestTemplate ())); }
kingoleg,

Zauważ też, że nowy InputStreamReader (responseWrapper.getBody (), StandardCharsets.UTF_8)); może zgłosić błąd, jeśli „drugi koniec” zwrócił błąd. Możesz umieścić go w bloku try.
PeterS,

16

---- lipiec 2019 ----

(przy użyciu Spring Boot)

Byłem zaskoczony, że Spring Boot, z całą magią konfiguracji zerowej, nie zapewnia łatwego sposobu na sprawdzenie lub zalogowanie prostej odpowiedzi JSON za pomocą RestTemplate. Przejrzałem różne podane tutaj odpowiedzi i komentarze i dzielę się własną destylowaną wersją tego, co (nadal) działa i wydaje mi się rozsądnym rozwiązaniem, biorąc pod uwagę obecne opcje (używam Spring Boot 2.1.6 z Gradle 4.4 )

1. Używanie Fiddlera jako proxy HTTP

Jest to w rzeczywistości dość eleganckie rozwiązanie, ponieważ omija wszystkie uciążliwe wysiłki związane z tworzeniem własnego przechwytywacza lub zmianą klienta HTTP na apache (patrz poniżej).

Zainstaluj i uruchom Fiddler

i wtedy

dodaj -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888do Opcji VM

2. Korzystanie z Apache HttpClient

Dodaj Apache HttpClient do swoich zależności Maven lub Gradle.

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

Użyj HttpComponentsClientHttpRequestFactoryjako RequestFactory dla RestTemplate. Najprostszym sposobem na to byłoby:

RestTemplate restTemplate = new RestTemplate();

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Włącz DEBUGĘ w swoim application.propertiespliku (jeśli używasz Spring Boot)

logging.level.org.apache.http=DEBUG

Jeśli korzystasz z Spring Boot, musisz upewnić się, że masz skonfigurowane środowisko rejestrowania, np. Używając zależności obejmującej rozruch rozruchowy spring-boot-starter-logging.

3. Użyj przechwytywacza

Pozwolę ci przeczytać propozycje, kontr-propozycje i gotchas w innych odpowiedziach i komentarzach i sam zdecydować, czy chcesz pójść tą ścieżką.

4. Adres URL dziennika i stan odpowiedzi bez treści

Chociaż nie spełnia to określonych wymagań dotyczących rejestrowania treści, jest to szybki i prosty sposób na rozpoczęcie rejestrowania połączeń REST. Wyświetla pełny adres URL i status odpowiedzi.

Po prostu dodaj następujący wiersz do swojego application.propertiespliku (zakładając, że używasz Spring Boot, i zakładając, że używasz zależności rozruchowej Spring, która obejmuje spring-boot-starter-logging)

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

Dane wyjściowe będą wyglądać mniej więcej tak:

2019-07-29 11:53:50.265 DEBUG o.s.web.client.RestTemplate : HTTP GET http://www.myrestservice.com/Endpoint?myQueryParam=myValue
2019-07-29 11:53:50.276 DEBUG o.s.web.client.RestTemplate : Accept=[application/json]
2019-07-29 11:53:50.584 DEBUG o.s.web.client.RestTemplate : Response 200 OK
2019-07-29 11:53:50.585 DEBUG o.s.web.client.RestTemplate : Reading to [org.mynamespace.MyJsonModelClass]

2
Nr 4 to najprostszy sposób na debugowanie.
Yubaraj,

1
Nr 2 pracował dla mnie. Loguje treść żądania. Dziękuję Ci!
caglar

1
Uznałem, że nr 3 jest łatwym sposobem na zrobienie tego, kiedy doszedłem do tego problemu.
Bill Naylor

12

Oprócz rejestrowania HttpClient opisanego w drugiej odpowiedzi można również wprowadzić ClientHttpRequestInterceptor, który odczytuje treść żądania i odpowiedź oraz rejestruje je. Możesz to zrobić, jeśli inne rzeczy również korzystają z HttpClient lub jeśli chcesz niestandardowego formatu rejestrowania. Uwaga: powinieneś nadać RestTemplate BufferingClientHttpRequestFactory, abyś mógł przeczytać odpowiedź dwa razy.


12

Jak stwierdzono w innych odpowiedziach, ciało odpowiedzi wymaga specjalnego traktowania, aby można było go wielokrotnie czytać (domyślnie jego zawartość jest konsumowana przy pierwszym czytaniu).

Zamiast używać BufferingClientHttpRequestFactorypodczas konfigurowania żądania, sam przechwytujący może zawinąć odpowiedź i upewnić się, że treść jest zachowana i może być wielokrotnie odczytywana (zarówno przez program rejestrujący, jak i przez konsumenta odpowiedzi):

Mój przechwytujący, który

  • buforuje treść odpowiedzi za pomocą opakowania
  • loguje się w bardziej kompaktowy sposób
  • rejestruje również identyfikator kodu statusu (np. 201 Utworzono)
  • zawiera numer sekwencyjny żądania, który pozwala łatwo odróżnić wpisy dziennika współbieżnego od wielu wątków

Kod:

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

Konfiguracja:

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

Przykładowe dane wyjściowe dziennika:

2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 >  Request: POST http://localhost:53969/payment/v4/private/payment-lists/10022/templates
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 >  Headers: {Accept=[application/json, application/json], Content-Type=[application/json;charset=UTF-8], Content-Length=[986]}
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 >  Body: 
{"idKey":null, ...}
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 <  Response: 200 OK 
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 <  Headers: {Content-Type=[application/json;charset=UTF-8], Transfer-Encoding=[chunked], Date=[Mon, 08 Oct 2018 08:58:53 GMT]}
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 <  Body: 
{ "idKey" : "10022", ...  }

1
Ten działa z wersją wiosenną 2019, utrzymując ciało w nienaruszonym stanie.
Udo odbył się

1
Działa na wiosnę 2.1.10 :) Dzięki
Moler

8

application.properties

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

application.yml

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

8

Może to nie być właściwy sposób, ale myślę, że jest to najprostsze podejście do drukowania żądań i odpowiedzi bez wypełniania zbyt dużej ilości dzienników.

Dodając poniżej 2 wierszy application.properties rejestruje wszystkie żądania i odpowiedzi 1. wiersz w celu zarejestrowania żądań i 2. wiersz w celu zarejestrowania odpowiedzi.

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

Rejestrowanie odpowiedzi nie działa dla mnie. Po prostu rejestruje kod statusu. Czy powinien rejestrować ładunek?
badera

Klasa HttpEntityMethodProcessor (v5.1.8) niczego nie rejestruje.
Chris

6

Zakładając, że RestTemplatejest skonfigurowany do używania HttpClient 4.x, możesz przeczytać dokumentację rejestrowania HttpClient tutaj . Rejestratory różnią się od tych określonych w innych odpowiedziach.

Konfiguracja rejestrowania dla HttpClient 3.x jest dostępna tutaj .


4

Tak wiele odpowiedzi tutaj wymaga zmian w kodowaniu i dostosowanych klas i to naprawdę nie jest konieczne. Pobierz debugujący serwer proxy, taki jak skrzypek, i skonfiguruj środowisko Java, aby korzystało z serwera proxy w wierszu poleceń (-Dhttp.proxyHost i -Dhttp.proxyPort), a następnie uruchom skrzynkę i możesz zobaczyć żądania i odpowiedzi w całości. Ma również wiele dodatkowych zalet, takich jak możliwość majstrowania przy wynikach i odpowiedziach przed i po wysłaniu ich w celu przeprowadzenia eksperymentów przed przystąpieniem do modyfikacji serwera.

Ostatni problem, który może się pojawić, jeśli musisz użyć HTTPS, musisz wyeksportować certyfikat SSL ze skrzypka i zaimportować go do magazynu kluczy Java (cacerts). Wskazówka: domyślne hasło do pliku kluczy Java to zwykle „zmień”.


1
To zadziałało dla mnie przy użyciu intellij i regularnej instalacji skrzypiec. Zredagowałem Uruchom konfigurację i ustawiłem opcje VM na -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888.
JD

Dzięki! Jest to dość eleganckie rozwiązanie w porównaniu do pisania własnego Interceptora.
Chris

3

Do logowania do Logback przy pomocy Apache HttpClient:

Potrzebujesz Apache HttpClient w ścieżce klasy:

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

Skonfiguruj RestTemplatedo używania HttpClient:

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Aby rejestrować żądania i odpowiedzi, dodaj do pliku konfiguracyjnego Logback:

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

Lub zalogować jeszcze więcej:

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

Jaki plik konfiguracyjny wylogowania?
G_V

1
@G_V logback.xml lub logback-test.xml do testów.
holmis83

Działa również org.apache.http.wire=DEBUGw twoim application.propertiesteraz
G_V

@G_V, jeśli używasz Spring-Boot. Moja odpowiedź działa bez rozruchu.
holmis83

2

Sztuczka polegająca na skonfigurowaniu RestTemplateza pomocą BufferingClientHttpRequestFactorynie działa, jeśli używasz takiego ClientHttpRequestInterceptor, co zrobisz, jeśli próbujesz zalogować się za pomocą przechwytywaczy. Wynika to ze sposobu, który InterceptingHttpAccessor(któryRestTemplate podklasy) działa.

Krótko mówiąc ... po prostu użyj tej klasy zamiast RestTemplate(pamiętaj, że używa interfejsu API rejestrowania SLF4J, edytuj w razie potrzeby):

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

Zgadzam się, że to głupie, że tyle pracy zajmuje tylko to.


2

Dodanie do powyższej dyskusji to tylko scenariusze Happy. prawdopodobnie nie będziesz w stanie zarejestrować odpowiedzi, jeśli wystąpi błąd .

W tym przypadku plus wszystkie powyższe przypadki musisz zastąpić DefaultResponseErrorHandler i ustawić go jak poniżej

restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());

2

O dziwo, żadne z tych rozwiązań nie działa, ponieważ wydaje się, że RestTemplate nie zwraca odpowiedzi na niektóre błędy 500x klienta i serwera. W takim przypadku należy je również zarejestrować, implementując ResponseErrorHandler w następujący sposób. Oto szkic kodu, ale rozumiesz:

Możesz ustawić ten sam przechwytywacz jak moduł obsługi błędów:

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

A przechwytywanie implementuje oba interfejsy:

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

Co się stanie, jeśli ciało jest danymi wieloczęściowymi / danymi formularza, czy istnieje prosty sposób na odfiltrowanie danych binarnych (zawartości pliku) z dziennika?
Łukasz

1

Jak wskazał @MilacH, w implementacji wystąpił błąd. Jeśli zostanie zwrócony kod statusu> 400, zgłoszony zostanie wyjątek IOException, ponieważ obiekt przechwytujący błędy nie jest wywoływany z przechwytywaczy. Wyjątek można zignorować, a następnie ponownie przechwycić w metodzie procedury obsługi.

package net.sprd.fulfillment.common;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

import static java.nio.charset.StandardCharsets.UTF_8;

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {

    final static Logger log = LoggerFactory.getLogger(LoggingRequestInterceptor.class);

    @SuppressWarnings("HardcodedLineSeparator")
    public static final char LINE_BREAK = '\n';

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        try {
            traceRequest(request, body);
        } catch (Exception e) {
            log.warn("Exception in LoggingRequestInterceptor while tracing request", e);
        }

        ClientHttpResponse response = execution.execute(request, body);

        try {
            traceResponse(response);
        } catch (IOException e) {
            // ignore the exception here, as it will be handled by the error handler of the restTemplate
            log.warn("Exception in LoggingRequestInterceptor", e);
        }
        return response;
    }

    private void traceRequest(HttpRequest request, byte[] body) {
        log.info("===========================request begin================================================");
        log.info("URI         : {}", request.getURI());
        log.info("Method      : {}", request.getMethod());
        log.info("Headers     : {}", request.getHeaders());
        log.info("Request body: {}", new String(body, UTF_8));
        log.info("==========================request end================================================");
    }

    private void traceResponse(ClientHttpResponse response) throws IOException {
        StringBuilder inputStringBuilder = new StringBuilder();
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), UTF_8))) {
            String line = bufferedReader.readLine();
            while (line != null) {
                inputStringBuilder.append(line);
                inputStringBuilder.append(LINE_BREAK);
                line = bufferedReader.readLine();
            }
        }

        log.info("============================response begin==========================================");
        log.info("Status code  : {}", response.getStatusCode());
        log.info("Status text  : {}", response.getStatusText());
        log.info("Headers      : {}", response.getHeaders());
        log.info("Response body: {}", inputStringBuilder);
        log.info("=======================response end=================================================");
    }

}

0

Najlepsze rozwiązanie teraz, po prostu dodaj zależność:

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

Zawiera klasę LoggingRequestInterceptor, którą można dodać w ten sposób do RestTemplate:

zintegruj to narzędzie, dodając je jako przechwytywacz do sprężynowego elementu RestTemplate, w następujący sposób:

restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build());

i dodaj implementację slf4j do swojego frameworka, np. log4j.

lub bezpośrednio użyj „Zg2proRestTemplate” . „Najlepsza odpowiedź” autorstwa @PaulSabou wygląda tak, ponieważ biblioteki httpclient i wszystkie biblioteki apache.http niekoniecznie są ładowane podczas korzystania ze sprężynowego szablonu RestTemplate.


jaka jest wydana wersja?
popalka

wydana wersja ma teraz 0.2
Moses Meyer

1
łatwość użycia jest świetna, ale brakuje nagłówków
WrRaThY

dodatkowo: wszystkie przydatne metody w LoggingRequestInterceptor są prywatne, co stanowi problem, jeśli chodzi o rozszerzenie (może być chronione)
WrRaThY

nie mogę niestety edytować komentarzy po 5 minutach. Wszystko, co musisz zrobić, aby zalogować się do nagłówków, to: log("Headers: {}", request.headers)in LoggingRequestInterceptor:traceRequesti log("Headers: {}", response.headers)in LoggingRequestInterceptor:logResponse. Możesz pomyśleć o dodaniu niektórych flag do rejestrowania nagłówków i treści. Ponadto - możesz sprawdzić typ zawartości treści do rejestrowania (na przykład rejestruj tylko aplikację / json *). To powinno być również konfigurowalne. Podsumowując, dzięki tym drobnym poprawkom będziesz mieć przyjemną bibliotekę do rozpowszechnienia. dobra robota :)
WrRaThY

0

Chciałem również dodać moją implementację tego. Przepraszam za wszystkie brakujące średniki, to jest napisane w Groovy.

Potrzebowałem czegoś bardziej konfigurowalnego niż podana zaakceptowana odpowiedź. Oto pozostałe szablony bean, które są bardzo zwinne i będą rejestrować wszystko tak, jak szuka OP.

Niestandardowa klasa przechwytywania rejestrowania:

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

Definicja szablonu fasoli:

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

Realizacja:

@Component
class RestService {

    private final RestTemplate restTemplate
    private final static Logger log = LoggerFactory.getLogger(RestService.class)

    @Autowired
    RestService(
            @Qualifier("myRestTemplate") RestTemplate restTemplate
    ) {
        this.restTemplate = restTemplate
    }

    // add specific methods to your service that access the GET and PUT methods

    private <T> T getForObject(String path, Class<T> object, Map<String, ?> params = [:]) {
        try {
            return restTemplate.getForObject(path, object, params)
        } catch (HttpClientErrorException e) {
            log.warn("Client Error (${path}): ${e.responseBodyAsString}")
        } catch (HttpServerErrorException e) {
            String msg = "Server Error (${path}): ${e.responseBodyAsString}"
            log.error(msg, e)
        } catch (RestClientException e) {
            String msg = "Error (${path})"
            log.error(msg, e)
        }
        return null
    }

    private <T> T putForObject(String path, T object) {
        try {
            HttpEntity<T> request = new HttpEntity<>(object)
            HttpEntity<T> response = restTemplate.exchange(path, HttpMethod.PUT, request, T)
            return response.getBody()
        } catch (HttpClientErrorException e) {
            log.warn("Error (${path}): ${e.responseBodyAsString}")
        } catch (HttpServerErrorException e) {
            String msg = "Error (${path}): ${e.responseBodyAsString}"
            log.error(msg, e)
        } catch (RestClientException e) {
            String msg = "Error (${path})"
            log.error(msg, e)
        }
        return null
    }
}


0

org.apache.http.wire dają zbyt nieczytelne logi, więc używam logbooka do rejestrowania aplikacji Servlet i RestTemplate req / resp do logowania

build.gradle

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

application.properties

logging.level.org.zalando.logbook:TRACE

RestTemplate

@Configuration
public class RestTemplateConfig {

@Autowired
private LogbookHttpRequestInterceptor logbookHttpRequestInterceptor;

@Autowired
private LogbookHttpResponseInterceptor logbookHttpResponseInterceptor;

@Bean
public RestTemplate restTemplate() {
    return new RestTemplateBuilder()
        .requestFactory(new MyRequestFactorySupplier())
        .build();
}

class MyRequestFactorySupplier implements Supplier<ClientHttpRequestFactory> {

    @Override
    public ClientHttpRequestFactory get() {
        // Using Apache HTTP client.
        CloseableHttpClient client = HttpClientBuilder.create()
            .addInterceptorFirst(logbookHttpRequestInterceptor)
            .addInterceptorFirst(logbookHttpResponseInterceptor)
            .build();
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(client);
        return clientHttpRequestFactory;
    }

}
}

-1

W związku z odpowiedzią za pomocą ClientHttpInterceptor znalazłem sposób na zachowanie całej odpowiedzi bez buforowania fabryk. Wystarczy zapisać strumień wejściowy treści odpowiedzi w tablicy bajtów za pomocą metody utils, która skopiuje tę tablicę z treści, ale ważne jest, aby otoczyć tę metodę metodą try catch, ponieważ pęknie, jeśli odpowiedź będzie pusta (to jest przyczyną wyjątku dostępu do zasobów) i w catch po prostu utwórz pustą tablicę bajtów, a następnie po prostu stwórz anonimową klasę wewnętrzną ClientHttpResponse przy użyciu tej tablicy i innych parametrów z oryginalnej odpowiedzi. Następnie możesz zwrócić ten nowy obiekt ClientHttpResponse do łańcucha wykonawczego pozostałych szablonów i możesz zarejestrować odpowiedź, używając wcześniej zapisanej tablicy bajtów treści. W ten sposób unikniesz zużycia InputStream w rzeczywistej odpowiedzi i możesz użyć odpowiedzi Rest Template w takiej formie, w jakiej jest. Uwaga,


-2

moja konfiguracja loggera używała xml

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

wtedy otrzymasz coś takiego:

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]

przez HttpMessageConverterExtractor.java:92, musisz kontynuować debugowanie, aw moim przypadku mam to:

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

i to:

outputMessage.getBody().flush();

outputMessage.getBody () zawiera komunikat wysyłany przez http (typ postu)


rejestrowanie śledzenia może być zbyt szczegółowe ... co jeśli tysiące żądań na sekundę ??
Gervasio Amy,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.