Spring RestTemplate - cara mengaktifkan debugging penuh / pencatatan permintaan / tanggapan?

220

Saya telah menggunakan Spring RestTemplate untuk sementara waktu dan saya secara konsisten menabrak dinding ketika saya mencoba men-debug permintaan dan tanggapannya. Saya pada dasarnya mencari untuk melihat hal yang sama seperti yang saya lihat ketika saya menggunakan ikal dengan opsi "verbose" dihidupkan. Sebagai contoh :

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

Akan menampilkan data yang dikirim dan data yang diterima (termasuk header, cookie, dll.).

Saya telah memeriksa beberapa posting terkait seperti: Bagaimana cara mencatat respons di Spring RestTemplate? tapi saya belum berhasil menyelesaikan masalah ini.

Salah satu cara untuk melakukan ini adalah dengan benar-benar mengubah kode sumber RestTemplate dan menambahkan beberapa pernyataan logging tambahan di sana, tapi saya akan menemukan pendekatan ini benar-benar hal terakhir. Seharusnya ada beberapa cara untuk memberi tahu Spring Web Client / RestTemplate untuk mencatat semuanya dengan cara yang lebih ramah.

Tujuan saya adalah dapat melakukan ini dengan kode seperti:

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

dan kemudian untuk mendapatkan jenis informasi debug yang sama (seperti yang saya dapatkan dengan curl) di file log atau di konsol. Saya percaya ini akan sangat berguna bagi siapa saja yang menggunakan Spring RestTemplate dan memiliki masalah. Menggunakan curl untuk men-debug masalah RestTemplate Anda tidak berfungsi (dalam beberapa kasus).

Paul Sabou
sumber
30
Peringatan kepada siapa pun yang membaca pada tahun 2018: Tidak ada jawaban sederhana untuk ini!
davidfrancis
3
Cara paling mudah adalah dengan menggunakan metode breakpoint in write (...) dari kelas AbstractHttpMessageConverter, ada objek outputMessage di mana Anda bisa melihat data. PS Anda dapat menyalin nilai dan kemudian memformatnya dengan formatter online.
Sergey Chepurnov
1
Sepertinya ini harus mudah dilakukan di Musim Semi, tetapi, menilai dari jawaban di sini - bukan itu masalahnya. Jadi salah satu solusi lain, akan memotong Spring sepenuhnya dan menggunakan alat seperti Fiddler untuk menangkap permintaan / respons.
michaelok
baca jawaban untuk pertanyaan ini dari tautan berikut: spring-resttemplate-how-to-enable-debugging-penuh-permintaan-tanggapan
Solanki Vaibhav
Juli 2019: Karena masih belum ada solusi sederhana untuk pertanyaan ini, saya mencoba untuk memberikan ringkasan dari 24 jawaban lainnya (sejauh ini) dan komentar serta diskusi mereka dalam jawaban saya sendiri di bawah ini . Semoga ini bisa membantu.
Chris

Jawaban:

206

Cukup untuk melengkapi contoh dengan implementasi penuh ClientHttpRequestInterceptoruntuk melacak permintaan dan respons:

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

}

Kemudian instantiate RestTemplatemenggunakan a BufferingClientHttpRequestFactorydan LoggingRequestInterceptor:

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

The BufferingClientHttpRequestFactorydiperlukan karena kami ingin menggunakan respon tubuh baik dalam pencegat dan untuk kode panggilan awal. Implementasi default memungkinkan untuk membaca badan respons hanya sekali.

sofiene zaghdoudi
sumber
27
Ini salah. Jika Anda membaca streaming, kode aplikasi tidak akan dapat membaca responsnya.
James Watkins
28
kami telah memberikan RestTemplate sebuah BufferingClientHttpRequestFactory sehingga kami dapat membaca responsnya dua kali.
sofiene zaghdoudi
16
Kami telah menggunakan teknik ini selama sekitar 3 bulan sekarang. Ini hanya bekerja dengan RestTemplate yang dikonfigurasi dengan BufferingClientHttpResponseWrapperseperti yang disiratkan @sofienezaghdoudi. Namun, itu tidak berfungsi ketika digunakan dalam tes menggunakan kerangka mockServer musim semi karena MockRestServiceServer.createServer(restTemplate)menimpa RequestFactory ke InterceptingClientHttpRequestFactory.
RubesMN
8
Tekniknya bagus, implementasi salah. 404 case, response.getBody () throw IOException -> Anda tidak pernah mendapatkan log untuk keluar dan bahkan yang terburuk, itu akan menjadi ResourceAccessException dalam kode lebih lanjut Anda, alih-alih RestClientResponseException
MilacH
5
Terima kasih balasannya. Tapi ini praktik yang buruk untuk memiliki banyak "log.debug" karena bisa tersebar di banyak log lainnya. Lebih baik menggunakan instruksi log.debug tunggal sehingga Anda yakin semuanya ada di tempat yang sama
user2447161
127

di Spring Boot Anda bisa mendapatkan permintaan / respons penuh dengan menyetelnya di properti (atau metode 12 faktor lainnya)

logging.level.org.apache.http=DEBUG

output ini

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

dan tanggapan

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

atau logging.level.org.apache.http.wire=DEBUGyang sepertinya mengandung semua informasi yang relevan

xenoterracide
sumber
4
Ini adalah hal paling sederhana yang melakukan apa yang saya inginkan. Saya sangat mendorong memasukkan ini dalam jawaban yang diterima.
michaelavila
22
Menurut javadoc dari 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 tidak menggunakan kelas-kelas Apache ini sebagai default, seperti yang ditunjukkan oleh @OrtomalaLokni, jadi Anda juga harus memasukkan cara menggunakannya selain cara mencetak debug ketika sedang digunakan.
Kapten Man
Saya mendapatkan seperti ini:http-outgoing-0 << "[0x1f][0x8b][0x8][0x0][0x0][0x0][0x0][0x0]
Partha Sarathi Ghosh
2
@ParthaSarathiGhosh Konten ini mungkin disandikan gzip yang mengapa Anda tidak melihat teks mentah.
Matius Buckett
80

Memperluas jawaban @hstoerr dengan beberapa kode:


Buat LoggingRequestInterceptor untuk mencatat respons permintaan

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

Setup RestTemplate

RestTemplate rt = new RestTemplate();

//set interceptors/requestFactory
ClientHttpRequestInterceptor ri = new LoggingRequestInterceptor();
List<ClientHttpRequestInterceptor> ris = new ArrayList<ClientHttpRequestInterceptor>();
ris.add(ri);
rt.setInterceptors(ris);
rt.setRequestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
mjj1409
sumber
Ini tidak tersedia sampai versi spring-3.1.
Gyan
3
itu tidak menjawab pertanyaan 'tanggapan logging', tetapi tinggalkan komentar // do logging sebagai gantinya.
Jiang YD
1
melakukan logging itu mudah, tetapi ini hanya berfungsi untuk permintaan, saya tidak melihat badan respons, misalkan saya memiliki objek respons, tetapi membaca aliran itu bukan ide yang baik.
Pavel Niedoba
11
@PavelNiedoba BufferClientHttpRequestFactory memungkinkan respons dibaca lebih dari satu kali.
mjj1409
2
Ini berfungsi baik jika Anda perlu menyimpan info tentang permintaan / respons dalam database untuk debugging dan pencatatan reguler tidak sesuai dengan kebutuhan Anda.
GameSalutes
32

Taruhan terbaik Anda adalah menambahkan logging.level.org.springframework.web.client.RestTemplate=DEBUGke application.propertiesfile.

Solusi lain seperti pengaturan log4j.logger.httpclient.wiretidak akan selalu berhasil karena mereka menganggap Anda menggunakan log4jdan Apache HttpClient, yang tidak selalu benar.

Namun, perhatikan bahwa sintaks ini hanya akan berfungsi pada versi terbaru dari Spring Boot.

gamliela
sumber
5
Ini bukan mencatat permintaan dan tanggapan tubuh, hanya url dan jenis permintaan (spring-web-4.2.6)
dve
1
Anda benar, ini bukan wirelogging, ini hanya mencakup informasi penting seperti url, kode resepone, parameter POST dll.
gamliela
1
apa yang sebenarnya Anda inginkan adalah stackoverflow.com/a/39109538/206466
xenoterracide
Ini baik-baik saja tetapi badan tanggapan tidak dapat dilihat!
sunleo
Cemerlang. Meskipun tidak mencetak isi respon, itu masih sangat berguna. Terima kasih.
Chris
30

Tidak satu pun dari jawaban ini yang benar-benar menyelesaikan 100% masalah. mjj1409 mendapatkan sebagian besar darinya, tetapi dengan mudah menghindari masalah logging respons, yang membutuhkan sedikit lebih banyak pekerjaan. Paul Sabou memberikan solusi yang tampaknya realistis, tetapi tidak memberikan detail yang cukup untuk benar-benar diterapkan (dan itu tidak berhasil sama sekali bagi saya). Sofiene mendapatkan pencatatan tetapi dengan masalah kritis: responsnya tidak lagi dapat dibaca karena aliran input telah digunakan!

Saya sarankan menggunakan BufferingClientHttpResponseWrapper untuk membungkus objek respons untuk memungkinkan membaca tubuh respons beberapa kali:

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

}

Ini tidak akan mengkonsumsi InputStream karena tubuh respons dimuat ke dalam memori dan dapat dibaca beberapa kali. Jika Anda tidak memiliki BufferingClientHttpResponseWrapper di classpath Anda, Anda dapat menemukan implementasi sederhana di sini:

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

Untuk mengatur RestTemplate:

LoggingRequestInterceptor loggingInterceptor = new LoggingRequestInterceptor();
restTemplate.getInterceptors().add(loggingInterceptor);
James Watkins
sumber
sama, responseCopy.getBody () melempar IOexception dalam kasus 404, sehingga Anda tidak pernah mengirim kembali ke kode lebih lanjut, respons dan biasanya RestClientResponseException menjadi ResourceAccessException
MilacH
1
Anda harus memeriksa status==200, sebelumresponseCopy.getBody()
Anand Rockzz
4
Tapi ini paket pribadi. Apakah Anda memasukkan LoggingRequestInterceptor Anda dalam paket 'org.springframework.http.client'?
zbstof
2
bagaimana asyncRestTemplate? Ini akan membutuhkan untuk mengembalikan ListenableFutureketika Anda mencegatnya yang tidak mungkin diubah dengan BufferingClientHttpResponseWrapperdalam panggilan balik.
Ömer Faruk Almali
@ ÖmerFarukAlmalı Dalam hal ini Anda harus menggunakan rantai atau mengubah tergantung pada versi Guava yang Anda gunakan. Lihat: stackoverflow.com/questions/8191891/…
James Watkins
29

Solusi yang diberikan oleh xenoterracide untuk digunakan

logging.level.org.apache.http=DEBUG

bagus tapi masalahnya adalah bahwa secara default Apache HttpComponents tidak digunakan.

Untuk menggunakan Apache HttpComponents tambahkan ke pom.xml Anda

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

dan konfigurasikan RestTemplatedengan:

RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory());
Ortomala Lokni
sumber
Cara termudah, saya hanya akan menambahkan bahwa itu tidak bekerja dengan MockRestServiceServer, karena menimpa requestFactory.
zbstof
Bekerja dengan baik dan tidak ada masalah konfigurasi kurang!
sunleo
29

Anda bisa menggunakan spring-rest-template-logger untuk loginRestTemplate lalu lintas HTTP.

Tambahkan ketergantungan ke proyek Maven Anda:

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

Kemudian sesuaikan RestTemplatesebagai berikut:

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

Pastikan bahwa debug logging diaktifkan di application.properties:

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

Sekarang semua lalu lintas HTTP RestTemplate akan dicatat org.hobsoft.spring.resttemplatelogger.LoggingCustomizer tingkat debug.

PENOLAKAN: Saya menulis perpustakaan ini.

Mark Hobson
sumber
Mengapa jawaban ini tidak dipilih? Itu membantu saya. Terima kasih, Mark Hobson.
Raffael Bechara Rameh
3
Senang itu membantu @RaffaelBecharaRameh. Awalnya dibatalkan karena saya tidak menanamkan instruksi dari proyek terkait. Jangan ragu untuk memilih jika Anda merasa itu berguna!
Mark Hobson
Apakah Anda mendukung via Gradle?
BlackHatSamurai
1
@BlackHatSamurai spring-rest-template-logger adalah artefak Maven biasa, jadi itu harus bekerja dengan baik dengan Gradle.
Mark Hobson
1
Hai @erhanasikoglu, terima kasih kembali! Itu benar, Anda dapat melihatnya sedang digunakan di sini: github.com/markhobson/spring-rest-template-logger/blob/master/…
Mark Hobson
26

Saya akhirnya menemukan cara untuk melakukan ini dengan cara yang benar. Sebagian besar solusi berasal dari Bagaimana cara mengkonfigurasi Spring dan SLF4J sehingga saya bisa mendapatkan logging?

Sepertinya ada dua hal yang perlu dilakukan:

  1. Tambahkan baris berikut di log4j.properties: log4j.logger.httpclient.wire=DEBUG
  2. Pastikan pegas tidak mengabaikan konfigurasi logging Anda

Masalah kedua kebanyakan terjadi pada lingkungan pegas di mana slf4j digunakan (seperti kasus saya). Dengan demikian, ketika slf4j digunakan pastikan bahwa dua hal berikut terjadi:

  1. Tidak ada pustaka penebangan commons di classpath Anda: ini bisa dilakukan dengan menambahkan deskriptor pengecualian di pom Anda:

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
  2. File log4j.properties disimpan di suatu tempat di classpath di mana pegas dapat menemukan / melihatnya. Jika Anda memiliki masalah dengan ini, solusi terakhir adalah dengan meletakkan file log4j.properties dalam paket default (bukan praktik yang baik tetapi hanya untuk melihat bahwa semuanya berjalan seperti yang Anda harapkan)

Paul Sabou
sumber
7
Ini tidak berhasil untuk saya, saya melakukan keduanya. Saya tidak mengerti mengapa saya harus meletakkan log4j.properties ketika itu tidak digunakan dalam proyek saya (diperiksa oleh mvn dependency: tree)
Pavel Niedoba
Ini tidak bekerja untuk saya juga. Saya bahkan mencoba mengatur root logger ke mode Debug dan masih tidak ada.
James Watkins
"httpclient.wire.content" dan "httpclient.wire.header" adalah nama logger dari kerangka kerja Axis2. Mereka dapat digunakan untuk mencatat misalnya permintaan SOAP dalam proyek Spring jika itu dilakukan dengan menggunakan Axis2.
lathspell
11
httpclient.wiresebenarnya dari perpustakaan Apache HttpComponents HttpClient (lihat hc.apache.org/httpcomponents-client-ga/logging.html ). Teknik ini hanya akan berfungsi jika Anda telah RestTemplatemengkonfigurasi untuk menggunakanHttpComponentsClientHttpRequestFactory
Scott Frederick
20

Log RestTemplate

Opsi 1. Buka logging debug.

Konfigurasikan RestTemplate

  • Secara default, RestTemplate bergantung pada fasilitas JDK standar untuk membuat koneksi HTTP. Anda dapat beralih untuk menggunakan pustaka HTTP yang berbeda seperti Apache HttpComponents

    @Bean publik RestTemplate restTemplate (pembuat RestTemplateBuilder) {RestTemplate restTemplate = builder.build (); mengembalikan restTemplate; }

Konfigurasikan pencatatan

  • application.yml

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

Opsi 2. Menggunakan Interceptor

Tanggapan Wrapper

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

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

public final class BufferingClientHttpResponseWrapper implements ClientHttpResponse {

    private final ClientHttpResponse response;

    private byte[] body;


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

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

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

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

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

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

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

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

}

Konfigurasikan RestTemplate

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

Konfigurasikan pencatatan

  • Periksa paket LoggingRestTemplate, misalnya dalam application.yml:

    logging: level: com.example.logging: DEBUG

Opsi 3. Menggunakan httpcomponent

Impor dependensi httpcomponent

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

Konfigurasikan RestTemplate

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

Konfigurasikan pencatatan

  • Periksa paket LoggingRestTemplate, misalnya dalam application.yml:

    logging: level: org.apache.http: DEBUG

pengguna2746033
sumber
Perhatikan: jika Anda ingin mengonfigurasi TestRestTemplate, konfigurasikan RestTemplateBuilder: @Bean public RestTemplateBuilder restTemplateBuilder () {return baru RestTemplateBuilder (). TambahanInterceptors (Collections.singletonList (new LoggingRestTemplate ())); }
kingoleg
Perhatikan juga bahwa InputStreamReader baru (responseWrapper.getBody (), StandardCharsets.UTF_8)); dapat melempar kesalahan jika "ujung lain" mengembalikan kesalahan. Anda mungkin ingin meletakkannya di blok percobaan.
PeterS
15

---- Juli 2019 ----

(menggunakan Spring Boot)

Saya terkejut bahwa Boot Spring, dengan semua sihir Konfigurasi Nol, tidak menyediakan cara mudah untuk memeriksa atau mencatat badan respons JSON sederhana dengan RestTemplate. Saya melihat melalui berbagai jawaban dan komentar yang diberikan di sini, dan saya membagikan versi suling saya sendiri tentang apa yang (masih) berfungsi dan menurut saya seperti solusi yang masuk akal, mengingat opsi saat ini (saya menggunakan Spring Boot 2.1.6 dengan Gradle 4.4 )

1. Menggunakan Fiddler sebagai proksi http

Ini sebenarnya solusi yang cukup elegan, karena mem-bypass semua upaya rumit untuk membuat interseptor Anda sendiri atau mengubah klien http yang mendasarinya menjadi apache (lihat di bawah).

Instal dan jalankan Fiddler

lalu

tambahkan -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888ke Opsi VM Anda

2. Menggunakan Apache HttpClient

Tambahkan Apache HttpClient ke dependensi Maven atau Gradle Anda.

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

Gunakan HttpComponentsClientHttpRequestFactorysebagai RequestFactory untuk RestTemplate. Cara paling sederhana untuk melakukannya adalah:

RestTemplate restTemplate = new RestTemplate();

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Aktifkan DEBUG di application.propertiesfile Anda (jika Anda menggunakan Spring Boot)

logging.level.org.apache.http=DEBUG

Jika Anda menggunakan Spring Boot, Anda harus memastikan bahwa Anda memiliki kerangka kerja logging, misalnya dengan menggunakan dependensi spring-boot-starter yang mencakup spring-boot-starter-logging.

3. Gunakan Pencegat

Saya akan membiarkan Anda membaca proposal, proposal tandingan, dan mendapat jawaban di jawaban dan komentar lainnya dan memutuskan sendiri apakah Anda ingin menempuh jalan itu.

4. Log URL dan Status Respons tanpa Badan

Meskipun ini tidak memenuhi persyaratan yang disebutkan untuk mencatat badan, itu adalah cara yang cepat dan sederhana untuk mulai mencatat panggilan REST Anda. Ini menampilkan URL lengkap dan status respons.

Cukup tambahkan baris berikut ke application.propertiesfile Anda (dengan asumsi Anda menggunakan Spring Boot, dan dengan asumsi Anda menggunakan dependensi starter boot spring yang mencakup spring-boot-starter-logging)

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

Outputnya akan terlihat seperti ini:

2019-07-29 11:53:50.265 DEBUG o.s.web.client.RestTemplate : HTTP GET http://www.myrestservice.com/Endpoint?myQueryParam=myValue
2019-07-29 11:53:50.276 DEBUG o.s.web.client.RestTemplate : Accept=[application/json]
2019-07-29 11:53:50.584 DEBUG o.s.web.client.RestTemplate : Response 200 OK
2019-07-29 11:53:50.585 DEBUG o.s.web.client.RestTemplate : Reading to [org.mynamespace.MyJsonModelClass]
Chris
sumber
2
Nomor 4 adalah cara termudah untuk melakukan debug.
Yubaraj
1
Nomor 2 bekerja untuk saya. Log tubuh permintaan. Terima kasih!
caglar
1
Saya menemukan No. 3 cara mudah untuk melakukan ini ketika saya sampai pada masalah ini.
Bill Naylor
12

Selain pencatatan HttpClient yang dijelaskan dalam jawaban lain , Anda juga dapat memperkenalkan ClientHttpRequestInterceptor yang membaca isi permintaan dan respons serta mencatatnya. Anda mungkin ingin melakukan ini jika hal-hal lain juga menggunakan HttpClient, atau jika Anda menginginkan format log kustom. Perhatian: Anda ingin memberikan RestTemplate BufferingClientHttpRequestFactory sehingga Anda dapat membaca responsnya dua kali.

Hans-Peter Störr
sumber
12

Seperti yang dinyatakan dalam respons lain, badan respons membutuhkan perlakuan khusus sehingga dapat dibaca berulang kali (secara default, isinya dikonsumsi pada pembacaan pertama).

Alih-alih menggunakan BufferingClientHttpRequestFactorysaat mengatur permintaan, pencegat itu sendiri dapat membungkus respons dan memastikan konten dipertahankan dan dapat dibaca berulang kali (oleh logger serta oleh konsumen respons):

Pencegat saya, yang

  • buffer tubuh respon menggunakan pembungkus
  • log dengan cara yang lebih kompak
  • mencatat pengidentifikasi kode status juga (mis. 201 Dibuat)
  • termasuk nomor urut permintaan yang memungkinkan untuk dengan mudah membedakan entri log bersamaan dari beberapa utas

Kode:

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

Konfigurasi:

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

Contoh keluaran log:

2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 >  Request: POST http://localhost:53969/payment/v4/private/payment-lists/10022/templates
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 >  Headers: {Accept=[application/json, application/json], Content-Type=[application/json;charset=UTF-8], Content-Length=[986]}
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 >  Body: 
{"idKey":null, ...}
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 <  Response: 200 OK 
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 <  Headers: {Content-Type=[application/json;charset=UTF-8], Transfer-Encoding=[chunked], Date=[Mon, 08 Oct 2018 08:58:53 GMT]}
2018-10-08 10:58:53 [main] DEBUG x.y.z.LoggingInterceptor - 2 <  Body: 
{ "idKey" : "10022", ...  }
Peter Walser
sumber
1
Yang ini bekerja dengan versi Musim Semi 2019 menjaga tubuh tetap utuh.
Udo Diadakan
1
Bekerja pada Musim Semi 2.1.10 :) Terima kasih
Moler
8

properti aplikasi

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

application.yml

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

Ini mungkin bukan cara yang benar untuk melakukannya, tetapi saya pikir ini adalah pendekatan paling sederhana untuk mencetak permintaan dan tanggapan tanpa mengisi terlalu banyak dalam log.

Dengan menambahkan di bawah 2 baris application.properties mencatat semua permintaan dan tanggapan baris ke-1 untuk mencatat permintaan dan baris ke-2 untuk mencatat respons.

logging.level.org.springframework.web.client.RestTemplate=DEBUG
logging.level.org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor=DEBUG
Viggi
sumber
Respons pencatatan tidak berfungsi untuk saya. Itu hanya Log Statuscode. Haruskah ia mencatat muatan?
badera
Kelas HttpEntityMethodProcessor (v5.1.8) tidak mencatat apa pun.
Chris
6

Asumsi RestTemplateyang dikonfigurasi untuk menggunakan HttpClient 4.x, Anda dapat membaca tentang dokumentasi logging HttpClient ini di sini . Para penebang berbeda dari yang ditentukan dalam jawaban lainnya.

Konfigurasi logging untuk HttpClient 3.x tersedia di sini .

Emerson Farrugia
sumber
4

Begitu banyak respons di sini yang membutuhkan perubahan koding dan kelas khusus dan itu benar-benar tidak perlu. Gte proxy debugging seperti fiddler dan atur lingkungan java Anda untuk menggunakan proxy pada baris perintah (-Dhttp.proxyHost dan -Dhttp.proxyPort) kemudian jalankan fiddler dan Anda dapat melihat permintaan dan respons secara keseluruhan. Juga hadir dengan banyak keuntungan tambahan seperti kemampuan untuk mengotak-atik hasil dan tanggapan sebelum dan setelah mereka dikirim untuk menjalankan percobaan sebelum melakukan modifikasi server.

Bit terakhir dari masalah yang dapat muncul adalah jika Anda harus menggunakan HTTPS, Anda harus mengekspor sertifikat SSL dari fiddler dan mengimpornya ke petunjuk java keystore (cacerts): kata sandi java keystore default biasanya "changeit".

Lee Burch
sumber
1
Ini bekerja untuk saya menggunakan intellij dan instalasi biola yang teratur. Saya mengedit Run Configuration dan mengatur opsi VM ke -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888.
JD
Terima kasih! Ini adalah solusi yang cukup elegan dibandingkan dengan menulis Interceptor Anda sendiri.
Chris
3

Untuk masuk ke Logback dengan bantuan dari Apache HttpClient:

Anda membutuhkan Apache HttpClient di classpath:

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

Konfigurasikan Anda RestTemplateuntuk menggunakan HttpClient:

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Untuk mencatat permintaan dan tanggapan, tambahkan ke file konfigurasi Logback:

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

Atau untuk lebih masuk:

<logger name="org.apache.http" level="DEBUG"/>
holmis83
sumber
Apa file konfigurasi logback?
G_V
1
@G_V logback.xml atau logback-test.xml untuk pengujian.
holmis83
Ini juga berfungsi dengan org.apache.http.wire=DEBUGAnda application.propertiessekarang
G_V
@ G_V jika Anda menggunakan Spring-Boot. Jawaban saya berfungsi tanpa Boot.
holmis83
2

Trik mengonfigurasi Anda RestTemplatedengan a BufferingClientHttpRequestFactorytidak berfungsi jika Anda menggunakan apa pun ClientHttpRequestInterceptor, yang akan Anda lakukan jika Anda mencoba masuk melalui pencegat. Ini karena cara yang InterceptingHttpAccessor(yang RestTemplatesubclass) bekerja.

Singkat cerita ... cukup gunakan kelas ini sebagai pengganti RestTemplate(perhatikan ini menggunakan API logging SLF4J, edit seperlunya):

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

Saya setuju itu konyol bahwa ini membutuhkan banyak pekerjaan hanya untuk melakukan ini.

Archie
sumber
2

Menambahkan ke diskusi di atas ini hanya merupakan skenario Selamat. mungkin Anda tidak akan dapat mencatat respons jika terjadi kesalahan .

Dalam hal ini ditambah semua kasus di atas Anda harus menimpa DefaultResponseErrorHandler dan mengaturnya seperti di bawah ini

restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());
pengguna666
sumber
2

Anehnya, tidak ada solusi yang berfungsi karena RestTemplate tampaknya tidak mengembalikan respons pada beberapa kesalahan 500x klien dan server. Dalam hal ini, Anda harus mencatatnya juga dengan menerapkan ResponseErrorHandler sebagai berikut. Berikut adalah konsep kode, tetapi Anda mendapatkan intinya:

Anda dapat mengatur interseptor yang sama dengan penangan kesalahan:

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

Dan intersep mengimplementasikan kedua antarmuka:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus.Series;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.ResponseErrorHandler;

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor, ResponseErrorHandler {
    static final Logger log = LoggerFactory.getLogger(LoggingRequestInterceptor.class);
    static final DefaultResponseErrorHandler defaultResponseErrorHandler = new DefaultResponseErrorHandler();
    final Set<Series> loggableStatuses = new HashSet();

    public LoggingRequestInterceptor() {
    }

    public LoggingRequestInterceptor(Set<Series> loggableStatuses) {
        loggableStatuses.addAll(loggableStatuses);
    }

    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        this.traceRequest(request, body);
        ClientHttpResponse response = execution.execute(request, body);
        if(response != null) {
            this.traceResponse(response);
        }

        return response;
    }

    private void traceRequest(HttpRequest request, byte[] body) throws IOException {
        log.debug("===========================request begin================================================");
        log.debug("URI         : {}", request.getURI());
        log.debug("Method      : {}", request.getMethod());
        log.debug("Headers     : {}", request.getHeaders());
        log.debug("Request body: {}", new String(body, "UTF-8"));
        log.debug("==========================request end================================================");
    }

    private void traceResponse(ClientHttpResponse response) throws IOException {
        if(this.loggableStatuses.isEmpty() || this.loggableStatuses.contains(response.getStatusCode().series())) {
            StringBuilder inputStringBuilder = new StringBuilder();

            try {
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), "UTF-8"));

                for(String line = bufferedReader.readLine(); line != null; line = bufferedReader.readLine()) {
                    inputStringBuilder.append(line);
                    inputStringBuilder.append('\n');
                }
            } catch (Throwable var5) {
                log.error("cannot read response due to error", var5);
            }

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

    }

    public boolean hasError(ClientHttpResponse response) throws IOException {
        return defaultResponseErrorHandler.hasError(response);
    }

    public void handleError(ClientHttpResponse response) throws IOException {
        this.traceResponse(response);
        defaultResponseErrorHandler.handleError(response);
    }
}
kisna
sumber
Bagaimana jika tubuh adalah multipart / form-data, apakah ada cara mudah untuk menyaring data biner (konten file) dari log?
Luke
1

Seperti @MilacH tunjukkan, ada kesalahan dalam implementasi. Jika statusCode> 400 dikembalikan, IOException dilemparkan, karena errorHandler tidak dipanggil, dari interseptor. Pengecualian dapat diabaikan dan kemudian ditangkap lagi dalam metode handler.

package net.sprd.fulfillment.common;

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

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

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

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {

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

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

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

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

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

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

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

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

}
Tony Findeisen
sumber
0

Solusi terbaik sekarang, tambahkan saja ketergantungan:

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

Ini berisi kelas LoggingRequestInterceptor yang bisa Anda tambahkan ke RestTemplate Anda:

mengintegrasikan utilitas ini dengan menambahkannya sebagai pencegat ke pegas RestTemplate, dengan cara berikut:

restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build());

dan tambahkan implementasi slf4j ke kerangka kerja Anda seperti log4j.

atau langsung menggunakan "Zg2proRestTemplate" . "Jawaban terbaik" oleh @PaulSabou tampak begitu, karena httpclient dan semua lib apache.http tidak selalu dimuat saat menggunakan spring RestTemplate.

Moses Meyer
sumber
apa versi yang dirilis?
popalka
versi yang dirilis sekarang adalah 0,2
Moses Meyer
1
kemudahan penggunaannya bagus, tetapi tidak memiliki tajuk
WrRaThY
tambahan: semua metode yang berguna di LoggingRequestInterceptor bersifat pribadi, yang merupakan masalah ketika datang ke ekstensi (bisa dilindungi)
WrRaThY
sayangnya, saya tidak dapat mengedit komentar setelah 5 menit. Yang harus Anda lakukan untuk mencatat header adalah ini: log("Headers: {}", request.headers)masuk LoggingRequestInterceptor:traceRequestdan log("Headers: {}", response.headers)masuk LoggingRequestInterceptor:logResponse. Anda mungkin ingin berpikir tentang menambahkan beberapa flag untuk mencatat header dan badan. Juga - Anda mungkin ingin memeriksa jenis konten tubuh untuk logging (misalnya aplikasi log saja / json *). Ini juga harus dapat dikonfigurasi. semua dalam semua, dengan tweak kecil Anda akan memiliki perpustakaan yang bagus untuk menyebar. kerja bagus :)
WrRaThY
0

Ingin menambahkan implementasi saya ini juga. Saya minta maaf untuk semua semi-titik dua yang hilang, ini ditulis dalam Groovy.

Saya membutuhkan sesuatu yang lebih dapat dikonfigurasi daripada jawaban yang diterima. Inilah kacang templat sisa yang sangat gesit dan akan mencatat semuanya seperti yang dicari OP.

Kelas Interceptor Penebangan Kustom:

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

Definisi Bean Templat Tempel:

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

Penerapan:

@Component
class RestService {

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

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

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

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

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

org.apache.http.wire memberikan log yang terlalu tidak dapat dibaca, jadi saya menggunakan logbook untuk mencatat aplikasi Servlet dan RestTemplate req / resp untuk login

build.gradle

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

properti aplikasi

logging.level.org.zalando.logbook:TRACE

RestTemplate

@Configuration
public class RestTemplateConfig {

@Autowired
private LogbookHttpRequestInterceptor logbookHttpRequestInterceptor;

@Autowired
private LogbookHttpResponseInterceptor logbookHttpResponseInterceptor;

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

class MyRequestFactorySupplier implements Supplier<ClientHttpRequestFactory> {

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

}
}
panser
sumber
-1

Terkait dengan respons menggunakan ClientHttpInterceptor, saya menemukan cara untuk menjaga seluruh tanggapan tanpa Buffering pabrik. Cukup simpan aliran input tubuh respons di dalam byte array menggunakan beberapa metode utils yang akan menyalin array itu dari tubuh, tetapi penting, kelilingi metode ini dengan try catch karena akan pecah jika respons kosong (itu adalah penyebab Resource Access Exception) dan di tangkapan hanya membuat array byte kosong, dan dari sekadar membuat kelas dalam anonim dari ClientHttpResponse menggunakan array itu dan parameter lain dari respons asli. Daripada Anda dapat mengembalikan objek ClientHttpResponse baru ke rantai eksekusi templat sisanya dan Anda dapat mencatat respons menggunakan array byte tubuh yang sebelumnya disimpan. Dengan begitu Anda akan menghindari mengkonsumsi InputStream dalam respons aktual dan Anda dapat menggunakan respons Rest Template apa adanya. Catatan,

NenadTzar
sumber
-2

konfigurasi logger saya menggunakan xml

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

maka Anda akan mendapatkan sesuatu seperti di bawah ini:

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]

melalui HttpMessageConverterExtractor.java:92, Anda perlu melanjutkan debug, dan dalam kasus saya, saya dapat ini:

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

dan ini:

outputMessage.getBody().flush();

outputMessage.getBody () berisi pesan yang dikirim http (tipe posting)

danshijin
sumber
melacak logging mungkin terlalu banyak bertele-tele ... bagaimana jika ada ribuan permintaan per detik ??
Gervasio Amy