Spring RestTemplate - Wie kann das vollständige Debuggen / Protokollieren von Anforderungen / Antworten aktiviert werden?


220

Ich benutze das Spring RestTemplate schon eine Weile und stoße ständig gegen eine Wand, wenn ich versuche, die Anfragen und Antworten zu debuggen. Grundsätzlich möchte ich die gleichen Dinge sehen, die ich sehe, wenn ich Curl mit aktivierter Option "Ausführlich" verwende. Zum Beispiel :

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

Zeigt sowohl die gesendeten als auch die empfangenen Daten an (einschließlich der Header, Cookies usw.).

Ich habe einige verwandte Beiträge überprüft wie: Wie protokolliere ich die Antwort in Spring RestTemplate? Aber ich habe es nicht geschafft, dieses Problem zu lösen.

Eine Möglichkeit, dies zu tun, wäre, den RestTemplate-Quellcode tatsächlich zu ändern und dort einige zusätzliche Protokollierungsanweisungen hinzuzufügen, aber ich würde diesen Ansatz wirklich als letzten Ausweg betrachten. Es sollte eine Möglichkeit geben, Spring Web Client / RestTemplate anzuweisen, alles viel freundlicher zu protokollieren.

Mein Ziel wäre es, dies mit Code wie folgt tun zu können:

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

und dann, um die gleiche Art von Debug-Informationen (wie bei Curl) in der Protokolldatei oder in der Konsole abzurufen. Ich glaube, dies wäre äußerst nützlich für alle, die Spring RestTemplate verwenden und Probleme haben. Die Verwendung von Curl zum Debuggen Ihrer RestTemplate-Probleme funktioniert einfach nicht (in einigen Fällen).


30
Warnung an alle, die 2018 lesen: Es gibt keine einfache Antwort darauf!
Davidfrancis

3
Am einfachsten ist es, einen Haltepunkt in der write (...) -Methode der AbstractHttpMessageConverter-Klasse zu verwenden. Es gibt ein outputMessage-Objekt, in dem Sie die Daten sehen können. PS Sie können den Wert kopieren und dann mit dem Online-Formatierer formatieren.
Sergey Chepurnov

1
Es scheint, dass dies im Frühjahr einfach sein sollte, aber nach den Antworten hier zu urteilen - nicht der Fall. Eine andere Lösung wäre, Spring vollständig zu umgehen und ein Tool wie Fiddler zu verwenden, um die Anforderung / Antwort zu erfassen.
Michaelok


Juli 2019: Da es für diese Frage noch keine einfache Lösung gibt, habe ich versucht, eine Zusammenfassung der anderen 24 Antworten (bisher) und ihrer Kommentare und Diskussionen in meiner eigenen Antwort unten zu geben . Ich hoffe es hilft.
Chris

Antworten:


206

Um das Beispiel mit einer vollständigen Implementierung zu vervollständigen ClientHttpRequestInterceptor, um Anforderung und Antwort zu verfolgen:

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

}

Dann instanziieren Sie RestTemplatemit a BufferingClientHttpRequestFactoryund LoggingRequestInterceptor:

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

Dies BufferingClientHttpRequestFactoryist erforderlich, da wir den Antworttext sowohl im Interceptor als auch für den anfänglichen aufrufenden Code verwenden möchten. Die Standardimplementierung erlaubt es, den Antworttext nur einmal zu lesen.


27
Das ist falsch. Wenn Sie den Stream lesen, kann der Anwendungscode die Antwort nicht lesen.
James Watkins

28
Wir haben dem RestTemplate eine BufferingClientHttpRequestFactory gegeben, damit wir die Antwort zweimal lesen können.
Sofiene Zaghdoudi

16
Wir verwenden diese Technik seit ungefähr 3 Monaten. Es funktioniert nur mit RestTemplate, das mit einem BufferingClientHttpResponseWrapperwie @sofienezaghdoudi impliziert konfiguriert ist . Es funktioniert jedoch nicht, wenn es in Tests mit dem mockServer-Framework von spring verwendet wird, da MockRestServiceServer.createServer(restTemplate)die RequestFactory auf überschrieben wird InterceptingClientHttpRequestFactory.
RubesMN

8
Die Technik ist gut, die Implementierung ist falsch. 404 case, response.getBody () löst IOException aus -> Sie erhalten nie das Protokoll für out und selbst im schlimmsten Fall wird es zu einer ResourceAccessException in Ihrem weiteren Code anstelle einer RestClientResponseException
MilacH

5
danke für die Antwort. Es ist jedoch eine schlechte Praxis, mehrere "log.debug" zu haben, da diese auf viele andere Protokolle verteilt sein können. Es ist besser, eine einzelne log.debug-Anweisung zu verwenden, damit Sie sicher sind, dass sich alles am selben Ort befindet
user2447161

127

In Spring Boot können Sie die vollständige Anforderung / Antwort erhalten, indem Sie dies in den Eigenschaften (oder einer anderen 12-Faktor-Methode) festlegen.

logging.level.org.apache.http=DEBUG

dies gibt aus

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

und Antwort

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

oder nur logging.level.org.apache.http.wire=DEBUGwelche scheint alle relevanten Informationen zu enthalten


4
Dies war die einfachste Sache, die tat, was ich wollte. Ich empfehle dringend, dies in die akzeptierte Antwort aufzunehmen.
Michaelavila

22
Laut dem Javadoc von 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 verwendet diese Apache-Klassen nicht als Standard, wie von @OrtomalaLokni hervorgehoben. Daher sollten Sie neben dem Drucken des Debugs bei der Verwendung auch angeben, wie sie verwendet werden sollen.
Captain Man

Ich werde so:http-outgoing-0 << "[0x1f][0x8b][0x8][0x0][0x0][0x0][0x0][0x0]
Partha Sarathi Ghosh

2
@ParthaSarathiGhosh Der Inhalt ist wahrscheinlich gzip-codiert, weshalb Sie den Rohtext nicht sehen.
Matthew Buckett

80

Erweitern Sie die Antwort von @hstoerr mit einem Code:


Erstellen Sie LoggingRequestInterceptor, um Anforderungsantworten zu protokollieren

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

Richten Sie RestTemplate ein

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

Dies ist erst in der Version Spring-3.1 verfügbar.
Gyan

3
Es beantwortet nicht die Frage der 'Protokollierungsantwort', sondern hinterlässt stattdessen einen // Protokollierungskommentar.
Jiang YD

1
Die Protokollierung war einfach, aber dies funktioniert nur für Anfragen. Ich sehe keine Antwortkörper. Angenommen, ich habe ein Antwortobjekt, aber das Lesen des Streams ist keine gute Idee.
Pavel Niedoba

11
@PavelNiedoba Mit der BufferClientHttpRequestFactory kann die Antwort mehrmals gelesen werden.
mjj1409

2
Dies funktioniert gut, wenn Sie Informationen zu Anforderungen / Antworten zum Debuggen in einer Datenbank speichern müssen und die regelmäßige Protokollierung nicht Ihren Anforderungen entspricht.
GameSalutes

32

Am besten fügen Sie logging.level.org.springframework.web.client.RestTemplate=DEBUGder application.propertiesDatei etwas hinzu.

Andere Lösungen wie das Einstellen log4j.logger.httpclient.wirefunktionieren nicht immer, da davon ausgegangen wird, dass Sie log4jApache verwenden HttpClient, was nicht immer der Fall ist.

Beachten Sie jedoch, dass diese Syntax nur bei den neuesten Versionen von Spring Boot funktioniert.


5
Dies protokolliert nicht den Anforderungs- und Antworttext, sondern nur die URL und den Anforderungstyp (spring-web-4.2.6)
dve

1
Sie haben Recht, es ist keine wireProtokollierung, es enthält nur wichtige Informationen wie URL, Resepone-Code, POST-Parameter usw.
Gamliela

1
Was Sie wirklich wollen, ist diese stackoverflow.com/a/39109538/206466
Xenoterracide

Dies ist in Ordnung, aber der Antwortkörper konnte nicht gesehen werden!
Sunleo

Brillant. Obwohl der Antworttext nicht gedruckt wird, ist er dennoch sehr nützlich. Danke.
Chris

30

Keine dieser Antworten löst tatsächlich 100% des Problems. mjj1409 bekommt das meiste davon, vermeidet jedoch bequemerweise das Problem der Protokollierung der Antwort, was etwas mehr Arbeit erfordert. Paul Sabou bietet eine Lösung, die realistisch erscheint, aber nicht genügend Details für die tatsächliche Implementierung bietet (und bei mir überhaupt nicht funktioniert hat). Sofiene hat die Protokollierung erhalten, aber mit einem kritischen Problem: Die Antwort ist nicht mehr lesbar, da der Eingabestream bereits verbraucht wurde!

Ich empfehle, einen BufferingClientHttpResponseWrapper zu verwenden, um das Antwortobjekt so zu verpacken, dass der Antworttext mehrmals gelesen werden kann:

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

}

Dadurch wird der InputStream nicht verbraucht, da der Antworttext in den Speicher geladen ist und mehrmals gelesen werden kann. Wenn Sie den BufferingClientHttpResponseWrapper nicht in Ihrem Klassenpfad haben, finden Sie die einfache Implementierung hier:

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

So richten Sie das RestTemplate ein:

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

Ebenso löst responseCopy.getBody () im Fall von 404 eine IOexception aus, sodass Sie die Antwort nie an Ihren weiteren Code zurücksenden und die normalerweise RestClientResponseException zu einer ResourceAccessException wird
MilacH

1
Sie sollten überprüfen status==200, bevorresponseCopy.getBody()
Anand Rockzz

4
Aber es ist paketprivat. Haben Sie Ihren LoggingRequestInterceptor in das Paket 'org.springframework.http.client' eingefügt?
Zbstof

2
was ist mit asyncRestTemplate? Es würde erforderlich sein, a zurückzugeben, ListenableFuturewenn Sie es abfangen, was BufferingClientHttpResponseWrapperin einem Rückruf nicht geändert werden kann.
Ömer Faruk Almalı

@ ÖmerFarukAlmalı In diesem Fall müssen Sie je nach verwendeter Guava-Version eine Kette oder Transformation verwenden. Siehe: stackoverflow.com/questions/8191891/…
James Watkins

29

Die von Xenoterracid gegebene Lösung ist zu verwenden

logging.level.org.apache.http=DEBUG

ist gut, aber das Problem ist, dass standardmäßig Apache HttpComponents nicht verwendet wird.

Um Apache HttpComponents zu verwenden, fügen Sie Ihrer pom.xml hinzu

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

und konfigurieren RestTemplatemit:

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

Am einfachsten möchte ich nur hinzufügen, dass es mit MockRestServiceServer nicht funktioniert, da es requestFactory überschreibt.
Zbstof

Funktioniert gut und keine Probleme weniger Konfiguration!
Sunleo

29

Sie können den Spring-Rest-Template-Logger verwenden , um den RestTemplateHTTP-Verkehr zu protokollieren .

Fügen Sie Ihrem Maven-Projekt eine Abhängigkeit hinzu:

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

Passen Sie dann Ihre RestTemplatewie folgt an:

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

Stellen Sie sicher, dass die Debug-Protokollierung aktiviert ist in application.properties:

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

Jetzt wird der gesamte HTTP-Verkehr von RestTemplate org.hobsoft.spring.resttemplatelogger.LoggingCustomizerauf Debug-Ebene protokolliert .

HAFTUNGSAUSSCHLUSS: Ich habe diese Bibliothek geschrieben.


Warum wird diese Antwort abgelehnt? Es hat mir geholfen. Danke, @ Mark Hobson.
Raffael Bechara Rameh

3
Ich bin froh, dass es @RaffaelBecharaRameh geholfen hat. Es wurde ursprünglich abgelehnt, weil ich keine Anweisungen aus dem verknüpften Projekt eingebettet habe. Fühlen Sie sich frei zu upvoten, wenn Sie es nützlich fanden!
Mark Hobson

Unterstützen Sie über Gradle?
BlackHatSamurai

1
@BlackHatSamurai Spring-Rest-Template-Logger ist ein reguläres Maven-Artefakt, daher sollte es mit Gradle gut funktionieren.
Mark Hobson

1
Hallo @erhanasikoglu, du bist willkommen! Das ist richtig, Sie können es hier verwenden: github.com/markhobson/spring-rest-template-logger/blob/master/…
Mark Hobson

26

Ich habe endlich einen Weg gefunden, dies richtig zu machen. Der größte Teil der Lösung stammt von Wie konfiguriere ich Spring und SLF4J, damit ich die Protokollierung erhalten kann?

Es scheint, dass zwei Dinge getan werden müssen:

  1. Fügen Sie die folgende Zeile in log4j.properties ein: log4j.logger.httpclient.wire=DEBUG
  2. Stellen Sie sicher, dass Spring Ihre Protokollierungskonfiguration nicht ignoriert

Das zweite Problem tritt hauptsächlich in Frühlingsumgebungen auf, in denen slf4j verwendet wird (wie es mein Fall war). Stellen Sie daher bei Verwendung von slf4j sicher, dass die folgenden zwei Dinge geschehen:

  1. In Ihrem Klassenpfad befindet sich keine Commons-Logging-Bibliothek. Dies kann durch Hinzufügen der Ausschlussdeskriptoren in Ihrem POM erfolgen:

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
  2. Die Datei log4j.properties wird irgendwo im Klassenpfad gespeichert, wo Spring sie finden / sehen kann. Wenn Sie Probleme damit haben, besteht eine letzte Möglichkeit darin, die Datei log4j.properties in das Standardpaket aufzunehmen (keine gute Vorgehensweise, aber nur um sicherzustellen, dass die Dinge wie erwartet funktionieren).


7
Das funktioniert bei mir nicht, ich habe beide Dinge getan. Ich verstehe nicht, warum ich log4j.properties einfügen muss, wenn es in meinem Projekt sowieso nicht verwendet wird (überprüft durch mvn-Abhängigkeit: Baum)
Pavel Niedoba

Das funktioniert auch bei mir nicht. Ich habe sogar versucht, den Root-Logger in den Debug-Modus zu versetzen und immer noch nichts.
James Watkins

"httpclient.wire.content" und "httpclient.wire.header" sind Loggernamen aus dem Axis2-Framework. Sie können verwendet werden, um z. B. SOAP-Anforderungen in einem Spring-Projekt zu protokollieren, wenn diese mit Axis2 ausgeführt werden.
Lathspell

11
httpclient.wirestammt tatsächlich aus der Apache HttpComponents HttpClient-Bibliothek (siehe hc.apache.org/httpcomponents-client-ga/logging.html ). Diese Technik funktioniert nur, wenn Sie die RestTemplateVerwendung vonHttpComponentsClientHttpRequestFactory
Scott Frederick

20

RestTemplate protokollieren

Option 1. Öffnen Sie die Debug-Protokollierung.

Konfigurieren Sie RestTemplate

  • Standardmäßig stützt sich das RestTemplate auf Standard-JDK-Funktionen, um HTTP-Verbindungen herzustellen. Sie können wechseln, um eine andere HTTP-Bibliothek wie Apache HttpComponents zu verwenden

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

Konfigurieren Sie die Protokollierung

  • application.yml

    Protokollierung: Ebene: org.springframework.web.client.RestTemplate: DEBUG

Option 2. Verwenden von Interceptor

Wrapper-Antwort

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

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

}

Konfigurieren Sie RestTemplate

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

Konfigurieren Sie die Protokollierung

  • Überprüfen Sie das Paket von LoggingRestTemplate, zum Beispiel in application.yml:

    logging: level: com.example.logging: DEBUG

Option 3. Verwenden von httpcomponent

Importieren Sie die httpcomponent-Abhängigkeit

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

Konfigurieren Sie RestTemplate

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

Konfigurieren Sie die Protokollierung

  • Überprüfen Sie das Paket von LoggingRestTemplate, zum Beispiel in application.yml:

    Protokollierung: Ebene: org.apache.http: DEBUG


Beachten Sie Folgendes : Wenn Sie TestRestTemplatekonfigurieren RestTemplateBuildermöchten , konfigurieren Sie : @Bean public RestTemplateBuilder restTemplateBuilder () {neuen RestTemplateBuilder () zurückgeben. AdditionalInterceptors (Collections.singletonList (new LoggingRestTemplate ())); }
Kingoleg

Beachten Sie auch, dass der neue InputStreamReader (responseWrapper.getBody (), StandardCharsets.UTF_8)); kann einen Fehler auslösen, wenn das "andere Ende" einen Fehler zurückgegeben hat. Möglicherweise möchten Sie es in einen Try-Block einfügen.
Peter

15

---- Juli 2019 ----

(mit Spring Boot)

Ich war überrascht, dass Spring Boot mit all seiner Magie der Nullkonfiguration keine einfache Möglichkeit bietet, einen einfachen JSON-Antworttext mit RestTemplate zu überprüfen oder zu protokollieren. Ich habe die verschiedenen Antworten und Kommentare hier durchgesehen und teile meine eigene destillierte Version dessen, was (noch) funktioniert, und scheint mir angesichts der aktuellen Optionen eine vernünftige Lösung zu sein (ich verwende Spring Boot 2.1.6 mit Gradle 4.4 )

1. Verwenden von Fiddler als http-Proxy

Dies ist eigentlich eine ziemlich elegante Lösung, da sie alle mühsamen Anstrengungen umgeht, einen eigenen Interceptor zu erstellen oder den zugrunde liegenden http-Client in Apache zu ändern (siehe unten).

Installieren Sie Fiddler und führen Sie es aus

und dann

Hinzufügen -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888zu Ihren VM-Optionen

2. Verwenden von Apache HttpClient

Fügen Sie Apache HttpClient zu Ihren Maven- oder Gradle-Abhängigkeiten hinzu.

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

Verwendung HttpComponentsClientHttpRequestFactoryals RequestFactory für RestTemplate. Der einfachste Weg, dies zu tun, wäre:

RestTemplate restTemplate = new RestTemplate();

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Aktivieren Sie DEBUG in Ihrer application.propertiesDatei (wenn Sie Spring Boot verwenden).

logging.level.org.apache.http=DEBUG

Wenn Sie Spring Boot verwenden, müssen Sie sicherstellen, dass ein Protokollierungsframework eingerichtet ist, z. B. mithilfe einer Spring-Boot-Starter-Abhängigkeit, die Folgendes enthält spring-boot-starter-logging.

3. Verwenden Sie einen Interceptor

Ich lasse Sie die Vorschläge, Gegenvorschläge und Fallstricke in den anderen Antworten und Kommentaren durchlesen und selbst entscheiden, ob Sie diesen Weg gehen möchten.

4. Protokollieren Sie die URL und den Antwortstatus ohne Text

Obwohl dies nicht den angegebenen Anforderungen für die Protokollierung des Körpers entspricht, können Sie schnell und einfach mit der Protokollierung Ihrer REST-Aufrufe beginnen. Es zeigt die vollständige URL und den Antwortstatus an.

Fügen Sie einfach die folgende Zeile zu Ihrer application.propertiesDatei hinzu (vorausgesetzt, Sie verwenden Spring Boot und verwenden eine Spring Boot-Starter-Abhängigkeit, die Folgendes enthält spring-boot-starter-logging).

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

Die Ausgabe sieht ungefähr so ​​aus:

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 ist der einfachste Weg zum Debuggen.
Yubaraj

1
Nr. 2 hat für mich gearbeitet. Es protokolliert den Hauptteil der Anfrage. Danke dir!
Caglar

1
Ich fand Nr. 3 eine einfache Möglichkeit, dies zu tun, als ich zu diesem Thema kam.
Bill Naylor

12

Neben der in der anderen Antwort beschriebenen HttpClient-Protokollierung können Sie auch einen ClientHttpRequestInterceptor einführen, der den Hauptteil der Anforderung und die Antwort liest und protokolliert. Möglicherweise möchten Sie dies tun, wenn andere Inhalte auch den HttpClient verwenden oder wenn Sie ein benutzerdefiniertes Protokollierungsformat wünschen. Achtung: Sie sollten dem RestTemplate eine BufferingClientHttpRequestFactory geben, damit Sie die Antwort zweimal lesen können.


12

Wie in den anderen Antworten angegeben, muss der Antwortkörper speziell behandelt werden, damit er wiederholt gelesen werden kann (standardmäßig wird sein Inhalt beim ersten Lesen verbraucht).

Anstatt das BufferingClientHttpRequestFactorybeim Einrichten der Anforderung zu verwenden, kann der Interceptor selbst die Antwort umbrechen und sicherstellen, dass der Inhalt beibehalten und wiederholt gelesen werden kann (sowohl vom Logger als auch vom Verbraucher der Antwort):

Mein Abfangjäger, der

  • puffert den Antworttext mithilfe eines Wrappers
  • protokolliert kompakter
  • protokolliert auch die Statuscode-ID (z. B. 201 Erstellt)
  • Enthält eine Anforderungssequenznummer , mit der gleichzeitige Protokolleinträge leicht von mehreren Threads unterschieden werden können

Code:

public class LoggingInterceptor implements ClientHttpRequestInterceptor {

    private final Logger log = LoggerFactory.getLogger(getClass());
    private AtomicInteger requestNumberSequence = new AtomicInteger(0);

    @Override
    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        int requestNumber = requestNumberSequence.incrementAndGet();
        logRequest(requestNumber, request, body);
        ClientHttpResponse response = execution.execute(request, body);
        response = new BufferedClientHttpResponse(response);
        logResponse(requestNumber, response);
        return response;
    }

    private void logRequest(int requestNumber, HttpRequest request, byte[] body) {
        if (log.isDebugEnabled()) {
            String prefix = requestNumber + " > ";
            log.debug("{} Request: {} {}", prefix, request.getMethod(), request.getURI());
            log.debug("{} Headers: {}", prefix, request.getHeaders());
            if (body.length > 0) {
                log.debug("{} Body: \n{}", prefix, new String(body, StandardCharsets.UTF_8));
            }
        }
    }

    private void logResponse(int requestNumber, ClientHttpResponse response) throws IOException {
        if (log.isDebugEnabled()) {
            String prefix = requestNumber + " < ";
            log.debug("{} Response: {} {} {}", prefix, response.getStatusCode(), response.getStatusCode().name(), response.getStatusText());
            log.debug("{} Headers: {}", prefix, response.getHeaders());
            String body = StreamUtils.copyToString(response.getBody(), StandardCharsets.UTF_8);
            if (body.length() > 0) {
                log.debug("{} Body: \n{}", prefix, body);
            }
        }
    }

    /**
     * Wrapper around ClientHttpResponse, buffers the body so it can be read repeatedly (for logging & consuming the result).
     */
    private static class BufferedClientHttpResponse implements ClientHttpResponse {

        private final ClientHttpResponse response;
        private byte[] body;

        public BufferedClientHttpResponse(ClientHttpResponse response) {
            this.response = response;
        }

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

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

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

        @Override
        public void close() {
            response.close();
        }

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

        @Override
        public HttpHeaders getHeaders() {
            return response.getHeaders();
        }
    }
}

Aufbau:

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

Beispiel für eine Protokollausgabe:

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
Dieser funktioniert mit der Frühjahrsversion 2019 und hält den Körper intakt.
Udo Held

1
Funktioniert am Frühling 2.1.10 :) Danke
Moler

8

application.properties

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

application.yml

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

8

Dies ist möglicherweise nicht der richtige Weg, aber ich denke, dies ist der einfachste Ansatz, um Anfragen und Antworten zu drucken, ohne zu viele Protokolle auszufüllen.

Durch Hinzufügen von 2 Zeilen protokolliert application.properties alle Anforderungen und Antworten in der ersten Zeile, um die Anforderungen zu protokollieren, und in der zweiten Zeile, um die Antworten zu protokollieren.

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

Das Protokollieren von Antworten funktioniert bei mir nicht. Es wird nur der Statuscode protokolliert. Sollte es Nutzdaten protokollieren?
Badera

Die Klasse HttpEntityMethodProcessor (v5.1.8) protokolliert nichts.
Chris

6

Angenommen RestTemplatewird konfiguriert Httpclient 4.x zu verwenden, können Sie bis auf Httpclient die Protokollierung Dokumentation lesen hier . Die Logger unterscheiden sich von den in den anderen Antworten angegebenen.

Die Protokollierungskonfiguration für HttpClient 3.x finden Sie hier .


4

So viele Antworten hier erfordern Codierungsänderungen und angepasste Klassen und es ist wirklich nicht notwendig. Holen Sie sich einen Debugging-Proxy wie Fiddler und stellen Sie Ihre Java-Umgebung so ein, dass der Proxy in der Befehlszeile (-Dhttp.proxyHost und -Dhttp.proxyPort) verwendet wird. Führen Sie dann Fiddler aus, und Sie können die Anforderungen und Antworten in ihrer Gesamtheit sehen. Darüber hinaus bietet es viele zusätzliche Vorteile, z. B. die Möglichkeit, die Ergebnisse und Antworten vor und nach dem Senden an Experimente zu basteln, bevor Änderungen am Server vorgenommen werden.

Das letzte Problem, das auftreten kann, ist, dass Sie, wenn Sie HTTPS verwenden müssen, das SSL-Zertifikat von Fiddler exportieren und in den Java-Keystore (Cacerts) importieren müssen. Hinweis: Das Standardkennwort für den Java-Keystore lautet normalerweise "changeit".


1
Dies funktionierte bei mir mit Intellij und der regulären Installation von Fiddle. Ich habe die Konfiguration ausführen ausgeführt und die VM-Optionen auf festgelegt -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888.
JD

Vielen Dank! Dies ist eine ziemlich elegante Lösung im Vergleich zum Schreiben eines eigenen Interceptors.
Chris

3

Für die Protokollierung bei Logback mit Hilfe von Apache HttpClient:

Sie benötigen Apache HttpClient im Klassenpfad:

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

Konfigurieren Sie Ihren RestTemplatefür die Verwendung von HttpClient:

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Fügen Sie der Protokollierungskonfigurationsdatei Folgendes hinzu, um Anforderungen und Antworten zu protokollieren:

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

Oder um noch mehr zu protokollieren:

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

Welche Logback-Konfigurationsdatei?
G_V

1
@G_V logback.xml oder logback-test.xml für Tests.
holmis83

Es funktioniert auch org.apache.http.wire=DEBUGin Ihrem application.propertiesJetzt
G_V

@G_V wenn Sie Spring-Boot verwenden. Meine Antwort funktioniert ohne Boot.
holmis83

2

Der Trick, Ihr RestTemplatemit einem zu konfigurieren, BufferingClientHttpRequestFactoryfunktioniert nicht, wenn Sie eines verwenden ClientHttpRequestInterceptor, was Sie tun werden, wenn Sie versuchen, sich über Interceptors anzumelden. Dies liegt an der Art und Weise, wie InterceptingHttpAccessor(welcheRestTemplate Unterklasse).

Lange RestTemplateRede, kurzer Sinn ... verwenden Sie einfach diese Klasse anstelle von (beachten Sie, dass hierfür die SLF4J-Protokollierungs-API verwendet wird, bearbeiten Sie sie nach Bedarf):

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

Ich stimme zu, es ist albern, dass es so viel Arbeit kostet, dies zu tun.


2

In der obigen Diskussion werden nur Happy-Szenarien dargestellt. Wahrscheinlich können Sie die Antwort nicht protokollieren, wenn ein Fehler auftritt.

In diesem Fall und in allen oben genannten Fällen müssen Sie DefaultResponseErrorHandler überschreiben und wie folgt festlegen

restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());

2

Seltsamerweise funktioniert keine dieser Lösungen, da RestTemplate bei einigen Client- und Server-500x-Fehlern anscheinend keine Antwort zurückgibt. In diesem Fall müssen Sie diese ebenfalls protokollieren, indem Sie ResponseErrorHandler wie folgt implementieren. Hier ist ein Codeentwurf, aber Sie verstehen:

Sie können denselben Interceptor wie den Fehlerbehandler festlegen:

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

Und der Intercept implementiert beide Schnittstellen:

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

Was ist, wenn der Körper aus mehrteiligen / Formulardaten besteht? Gibt es eine einfache Möglichkeit, Binärdaten (Dateiinhalt) aus dem Protokoll herauszufiltern?
Luke

1

Wie @MilacH hervorhob, liegt ein Fehler in der Implementierung vor. Wenn ein statusCode> 400 zurückgegeben wird, wird eine IOException von Interceptors ausgelöst, da der errorHandler nicht aufgerufen wird. Die Ausnahme kann ignoriert werden und wird dann in der Handler-Methode erneut abgefangen.

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

Beste Lösung jetzt, fügen Sie einfach Abhängigkeit hinzu:

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

Es enthält eine LoggingRequestInterceptor-Klasse, die Sie auf diese Weise zu Ihrer RestTemplate hinzufügen können:

Integrieren Sie dieses Dienstprogramm, indem Sie es wie folgt als Interceptor zu einem Spring RestTemplate hinzufügen:

restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build());

und fügen Sie Ihrem Framework eine slf4j-Implementierung wie log4j hinzu.

oder verwenden Sie direkt "Zg2proRestTemplate" . Die "beste Antwort" von @PaulSabou sieht so aus, da httpclient und alle apache.http-Bibliotheken bei Verwendung einer Spring RestTemplate nicht unbedingt geladen werden.


Was ist die veröffentlichte Version?
Popalka

Die veröffentlichte Version ist jetzt 0.2
Moses Meyer

1
Benutzerfreundlichkeit ist großartig, aber es fehlen Header
WrRaThY

Zusätzlich: Alle nützlichen Methoden in LoggingRequestInterceptor sind privat, was ein Problem bei der Erweiterung darstellt (könnte geschützt werden)
WrRaThY

Leider kann ich nach 5 Minuten keine Kommentare mehr bearbeiten. Alles, was Sie tun müssen, um Header zu protokollieren, ist Folgendes: log("Headers: {}", request.headers)In LoggingRequestInterceptor:traceRequestund log("Headers: {}", response.headers)In LoggingRequestInterceptor:logResponse. Möglicherweise möchten Sie einige Flags zum Protokollieren von Headern und Text hinzufügen. Außerdem möchten Sie möglicherweise den Inhaltstyp des Körpers für die Protokollierung überprüfen (z. B. nur Anwendung / json * protokollieren). Dies sollte auch konfigurierbar sein. Alles in allem haben Sie mit diesen kleinen Verbesserungen eine schöne Bibliothek zum Verbreiten. gute Arbeit :)
WrRaThY

0

Wollte auch meine Implementierung hinzufügen. Ich entschuldige mich für all die fehlenden Semikolons, dies ist in Groovy geschrieben.

Ich brauchte etwas Konfigurierbareres als die akzeptierte Antwort. Hier ist eine Rest Template Bean, die sehr agil ist und alles protokolliert, was das OP sucht.

Benutzerdefinierte Protokollierungs-Interceptor-Klasse:

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

Rest Template Bean Definition:

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

Implementierung:

@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 gibt zu unlesbare Protokolle, daher verwende ich das Logbuch , um das Anwendungsservlet und RestTemplate zu protokollieren

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

Im Zusammenhang mit der Antwort mit ClientHttpInterceptor habe ich einen Weg gefunden, die gesamte Antwort ohne Pufferfabriken beizubehalten. Speichern Sie einfach den Eingabestream des Antworttextes im Byte-Array mit einer Utils-Methode, die dieses Array aus dem Text kopiert. Wichtig ist jedoch, dass Sie diese Methode mit try catch umgeben, da sie unterbrochen wird, wenn die Antwort leer ist (dies ist die Ursache für die Ausnahme des Ressourcenzugriffs) und Erstellen Sie in catch einfach ein leeres Byte-Array und anschließend eine anonyme innere Klasse von ClientHttpResponse mit diesem Array und anderen Parametern aus der ursprünglichen Antwort. Anschließend können Sie das neue ClientHttpResponse-Objekt an die Ausführungskette der Restvorlage zurückgeben und die Antwort mithilfe des zuvor gespeicherten Body-Byte-Arrays protokollieren. Auf diese Weise vermeiden Sie, dass InputStream in der eigentlichen Antwort verwendet wird, und Sie können die Antwort "Rest Template" unverändert verwenden. Hinweis,


-2

Meine Logger-Konfiguration hat XML verwendet

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

dann bekommst du so etwas wie unten:

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]

über HttpMessageConverterExtractor.java:92 müssen Sie weiterhin debuggen, und in meinem Fall habe ich Folgendes erhalten:

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

und das:

outputMessage.getBody().flush();

outputMessage.getBody () enthält die Nachricht, die http (Post-Typ) sendet


Trace-Protokollierung ist möglicherweise zu ausführlich ... Was ist, wenn Tausende von Anfragen pro Sekunde eingehen?
Gervasio Amy
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.