Spring RestTemplate - як увімкнути повну налагодження / реєстрацію запитів / відповідей?


220

Я деякий час використовую Spring RestTemplate, і я послідовно вдаряю об стіну, коли я намагаюся налагодити її запити та відповіді. Я в основному шукаю бачити ті самі речі, що і я, коли використовую curl із увімкненою опцією «багатослів’я». Наприклад :

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

Буде відображено як надіслані дані, так і отримані дані (включаючи заголовки, файли cookie тощо).

Я перевірив декілька пов’язаних публікацій, таких як: Як занотувати відповідь у Spring RestTemplate? але мені не вдалося вирішити це питання.

Один із способів зробити це - насправді змінити вихідний код RestTemplate і додати туди додаткові заяви про реєстрацію, але я вважаю цей підхід справді крайнім заходом. Має бути якийсь спосіб сказати Spring Web Client / RestTemplate, щоб увійти все набагато дружніше.

Моєю метою було б мати можливість це зробити з таким кодом:

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

а потім отримати той самий тип інформації про налагодження (як я отримую з curl) у файлі журналу або в консолі. Я вважаю, що це було б дуже корисно для тих, хто використовує Spring RestTemplate і має проблеми. Використання curl для налагодження Ваших проблем RestTemplate просто не працює (в деяких випадках).


31
Попередження для всіх, хто читає у 2018 році: На це немає простої відповіді!
davidfrancis

3
Найпростіший спосіб - використовувати метод точки перерви в класі (()) класу AbstractHttpMessageConverter, є об'єкт outputMessage, де ви могли бачити дані. PS Ви можете скопіювати значення, а потім відформатувати його за допомогою онлайн-форматера.
Сергій Чепурнов

1
Здається, це слід зробити весною, але, судячи з відповідей тут - не так. Отже, іншим рішенням було б повністю обійти Spring і використовувати такий інструмент, як Fiddler, щоб захопити запит / відповідь.
michaelok


Липень 2019: Оскільки досі не існує простого вирішення цього питання, я спробував дати короткий підсумок інших 24 відповідей (поки що) та їх коментарі та дискусії у власній відповіді нижче . Сподіваюся, це допомагає.
Кріс

Відповіді:


207

Просто для завершення прикладу з повною реалізацією ClientHttpRequestInterceptorпростежити запит та відповідь:

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

}

Тоді інстанціюйте RestTemplateза допомогою а BufferingClientHttpRequestFactoryта LoggingRequestInterceptor:

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

BufferingClientHttpRequestFactoryПотрібно , як ми хочемо , щоб використовувати тіло відповіді як в перехоплювач і для вихідного коду виклику. Реалізація за замовчуванням дозволяє прочитати орган відповіді лише один раз.


27
Це неправильно. Якщо ви прочитаєте потік, код програми не зможе прочитати відповідь.
Джеймс Уоткінс

28
ми надали RestTemplate BufferingClientHttpRequestFactory, щоб ми могли прочитати відповідь двічі.
sofiene zaghdoudi

16
Ми використовуємо цю техніку вже близько 3 місяців. Він працює лише з RestTemplate, налаштованим BufferingClientHttpResponseWrapperяк @sofienezaghdoudi. Однак він не працює, коли використовується в тестах, що використовують весняний фреймворк mockServer, оскільки він MockRestServiceServer.createServer(restTemplate)перезаписує RequestFactory на InterceptingClientHttpRequestFactory.
RubesMN

8
Техніка хороша, реалізація неправильна. 404 case, response.getBody () кинути IOException -> ви ніколи не отримаєте журнал для виходу, а ще гірше, він стане ResourceAccessException у вашому подальшому коді, замість RestClientResponseException
MilacH

6
Дякую за відповідь. Але це погана практика мати кілька "log.debug", оскільки це може бути поширене на безліч інших журналів. Краще скористатись однією інструкцією log.debug, щоб ви впевнені, що все там же
user2447161

129

у Spring Boot ви можете отримати повний запит / відповідь, встановивши це у властивості (або інший 12-факторний метод)

logging.level.org.apache.http=DEBUG

це виводи

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

і відповідь

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

або просто logging.level.org.apache.http.wire=DEBUGякий, здається, містить усю відповідну інформацію


4
Це було найпростішим, що робив те, що я хотів. Я настійно рекомендую включити це у прийняту відповідь.
michaelavila

22
За повідомленням javadoc від RestTemplate :by default the RestTemplate relies on standard JDK facilities to establish HTTP connections. You can switch to use a different HTTP library such as Apache HttpComponents
Ortomala Lokni

22
RestTemplate не використовує ці класи Apache за замовчуванням, як вказував @OrtomalaLokni, тому ви також повинні вказати, як їх використовувати, а також як надрукувати налагодження при їх використанні.
Людина капітана

Мені стає так:http-outgoing-0 << "[0x1f][0x8b][0x8][0x0][0x0][0x0][0x0][0x0]
Partha Sarathi Ghosh

2
@ParthaSarathiGhosh Вміст, ймовірно, закодований у форматі gzip, тому ви не бачите необробленого тексту.
Меттью Бакетт

79

Розширення відповіді @hstoerr з деяким кодом:


Створіть LoggingRequestInterceptor для реєстрації запитів на відповіді

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

Налаштування 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());

Це недоступно до версії весни-3.1.
Gyan

3
він не відповідає на питання "реєстрація відповіді", але залишить замість цього коментар // do log.
Цзян ЙД

1
зробити журнал було легко, але це працює лише для запитів, я не бачу органів відповідей, припустимо, у мене є об’єкт відповіді, але читати його потоку - це не гарна ідея.
Павло Нієдоба

11
@PavelNiedoba BufferClientHttpRequestFactory дозволяє прочитати відповідь не один раз.
mjj1409

2
Це добре працює, якщо вам потрібно зберігати інформацію про запит / відповідь у базі даних для налагодження та регулярного ведення журналу не відповідає вашим потребам.
GameSalutes

32

Ваша найкраща ставка - додати logging.level.org.springframework.web.client.RestTemplate=DEBUGдо application.propertiesфайлу.

Інші рішення, як-от налаштування log4j.logger.httpclient.wire, не завжди спрацюють, оскільки вони передбачають, що ви використовуєте log4jта Apache HttpClient, що не завжди відповідає дійсності.

Однак зауважте, що цей синтаксис буде працювати лише в останніх версіях Spring Boot.


5
Це не реєстрація органу запиту та відповіді, просто URL-адреса та тип запиту (spring-web-4.2.6)
дві

1
Ви маєте рацію, це не wireкаротаж, вона включає в себе тільки необхідну інформацію , як URL, resepone код, параметри POST і т.д.
gamliela

1
що ви дійсно хочете це stackoverflow.com/a/39109538/206466
xenoterracide

Це нормально, але відповідь органу не було видно!
sunleo

Блискуча. Хоча він не друкує орган відповіді, він все ще дуже корисний. Спасибі.
Кріс

30

Жодна з цих відповідей насправді не вирішує 100% проблеми. mjj1409 отримує більшу частину цього, але зручно уникати питання реєстрації відповіді, що вимагає трохи більше роботи. Пол Сабу пропонує рішення, яке здається реалістичним, але не надає достатньо деталей, щоб реально реалізувати (а для мене це зовсім не працювало). Sofiene отримала реєстрацію, але з критичною проблемою: відповідь більше не читається, оскільки вхідний потік вже спожитий!

Я рекомендую використовувати BufferingClientHttpResponseWrapper, щоб обернути об'єкт відповіді, щоб дозволити прочитати тіло відповіді кілька разів:

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

}

Це не споживає InputStream, оскільки тіло відповідей завантажується в пам'ять і може бути прочитане кілька разів. Якщо у вас немає BufferingClientHttpResponseWrapper на своєму classpath, ви можете знайти просту реалізацію тут:

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

Для налаштування шаблону RestTemplate:

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

те саме, responseCopy.getBody () кидає IOexception у випадку 404, тому ви ніколи не відсилаєте на свій наступний код відповідь, і зазвичай RestClientResponseException стає ResourceAccessException
MilacH

1
Слід перевірити status==200, ранішеresponseCopy.getBody()
Ананд Рокз

4
Але це пакетно-приватне. Ви помістили свій LoggingRequestInterceptor в пакет "org.springframework.http.client"?
zbstof

2
про що asyncRestTemplate? Потрібно повернути a, ListenableFutureколи ви перехопите його, що неможливо змінити BufferingClientHttpResponseWrapperв зворотному дзвінку.
Ömer Faruk Almalı

@ ÖmerFarukAlmalı У цьому випадку вам потрібно буде використовувати ланцюжок або перетворити залежно від версії Guava, яку ви використовуєте. Дивіться: stackoverflow.com/questions/8191891/…
Джеймс Уоткінс

30

Ви можете використовувати весна-відпочинок-реєстратор шаблонів для реєстрації RestTemplateтрафіку HTTP.

Додайте залежність до свого проекту Maven:

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

Потім налаштуйте RestTemplateтак:

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

Переконайтесь, що журнал налагодження включений у application.properties:

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

Тепер весь HTTP-трафік RestTemplate буде зареєстровано на org.hobsoft.spring.resttemplatelogger.LoggingCustomizerрівні налагодження.

ВІДМОВА: Я написав цю бібліотеку.


Чому ця відповідь спростована? Це мені допомогло. Дякую, @Mark Hobson
Раффаель Бечара Раме

3
Радий, що це допомогло @RaffaelBecharaRameh. Спочатку це було знято, тому що я не вставляв інструкції з пов'язаного проекту. Не соромтеся подати заявку, якщо вважаєте це корисним!
Марк Хобсон

Чи підтримуєте ви через Gradle?
BlackHatSamurai

1
@BlackHatSamurai весна-відпочинок-реєстратор шаблонів - це звичайний артефакт Maven, тому він повинен добре працювати з Gradle.
Марк Хобсон

1
Привіт @erhanasikoglu, ласкаво просимо! Це правильно, ви можете побачити його тут: github.com/markhobson/spring-rest-template-logger/blob/master/…
Марк Хобсон

29

Розчин, наданий ксенотерацидом для використання

logging.level.org.apache.http=DEBUG

це добре, але проблема полягає в тому, що за замовчуванням Apache HttpComponents не використовується.

Щоб використовувати Apache HttpComponents, додайте у свій pom.xml

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

і налаштувати за RestTemplateдопомогою:

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

Найпростіший спосіб, я лише додам, що він не працює з MockRestServiceServer, оскільки він перезаписує requestFactory.
zbstof

Працює добре і не має менш конфігурації!
sunleo

26

Я нарешті знайшов спосіб зробити це правильним чином. Більшість рішень походить з Як налаштувати Spring та SLF4J, щоб я міг вести журнал?

Здається, що потрібно зробити дві речі:

  1. Додайте наступний рядок у log4j.properties: log4j.logger.httpclient.wire=DEBUG
  2. Переконайтеся, що весна не ігнорує ваш конфігурацію журналу

Другий випуск трапляється здебільшого у весняних середовищах, де використовується slf4j (як це було в моєму випадку). Таким чином, коли використовується slf4j, переконайтеся, що трапляються наступні дві речі:

  1. У вашому класі не існує бібліотеки обміну журналами: це можна зробити, додавши дескриптори виключення у ваш пом:

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
  2. Файл log4j.properties зберігається десь у класі шляху, де весна може його знайти / побачити. Якщо у вас виникли проблеми з цим, крайнім рішенням було б помістити файл log4j.properties в пакет за замовчуванням (не є хорошою практикою, але просто щоб побачити, як все працює так, як ви очікували)


7
Це не працює для мене, я робив обидві речі. Я не розумію, навіщо мені ставити log4j.properties, коли він все одно не використовується в моєму проекті (перевірено залежністю від mvn: tree)
Павло Нієдоба

Це також не працює для мене. Я навіть спробував встановити кореневий реєстратор в режим налагодження і все ще нічого.
Джеймс Уоткінс

"httpclient.wire.content" і "httpclient.wire.header" - це імена реєстраторів із рамки Axis2. Вони можуть використовуватися для реєстрації, наприклад, запитів SOAP у проекті Spring, якщо вони виконуються за допомогою Axis2.
lathspell

11
httpclient.wireнасправді з бібліотеки HttpClient Apache HttpComponents (див. hc.apache.org/httpcomponents-client-ga/logging.html ). Ця методика працюватиме лише у тому випадку, якщо ви RestTemplateналаштували на використанняHttpComponentsClientHttpRequestFactory
Скотт Фредерік

22

Ведення журналу RestTemplate

Варіант 1. Відкрити журнал налагодження.

Налаштуйте шаблон RestTemplate

  • За замовчуванням RestTemplate покладається на стандартні засоби JDK для встановлення HTTP-з'єднань. Ви можете переключитися на використання іншої бібліотеки HTTP, наприклад Apache HttpComponents

    @Bean public RestTemplate restTemplate (конструктор RestTemplateBuilder) {RestTemplate restTemplate = builder.build (); повернути відпочинкуTemplate; }

Налаштування журналу

  • application.yml

    реєстрація: рівень: org.springframework.web.client.RestTemplate: DEBUG

Варіант 2. Використання перехоплювача

Відповідь обгортки

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

Реалізатор перехоплення

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

}

Налаштуйте шаблон RestTemplate

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

Налаштування журналу

  • Перевірте пакет LoggingRestTemplate, наприклад у application.yml:

    реєстрація: рівень: com.example.logging: DEBUG

Варіант 3. Використання httpcomponent

Імпортуйте залежність httpcomponent

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

Налаштуйте шаблон RestTemplate

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

Налаштування журналу

  • Перевірте пакет LoggingRestTemplate, наприклад у application.yml:

    реєстрація: рівень: org.apache.http: DEBUG


Лише зауважте: якщо ви хочете налаштувати TestRestTemplate, налаштуйте RestTemplateBuilder: @Bean public RestTemplateBuilder restTemplateBuilder () {поверніть новий RestTemplateBuilder (). AdditionalInterceptors (Collections.singletonList (новий LoggingRestTemplate ())); }
kingoleg

Зауважте також, що новий InputStreamReader (responseWrapper.getBody (), StandardCharsets.UTF_8)); може видалити помилку, якщо "інший кінець" повернув помилку. Ви можете розмістити його в блоці спробу.
PeterS

16

---- липень 2019 ----

(використовуючи Spring Boot)

Я був здивований, що Spring Boot, з усією магією Zero Configuration, не забезпечує простий спосіб перевірити або записати простий корпус відповіді JSON з RestTemplate. Я переглянув різні відповіді та коментарі, надані тут, і ділюсь власною дистильованою версією того, що (досі) працює, і мені здається розумним рішенням, враховуючи поточні параметри (я використовую Spring Boot 2.1.6 з Gradle 4.4 )

1. Використання Fiddler як http-проксі

Це насправді досить елегантне рішення, оскільки воно обходить усі громіздкі зусилля щодо створення власного перехоплювача або зміни базового http-клієнта на апаш (див. Нижче).

Встановити та запустити Fiddler

і потім

додати -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888до Ваших параметрів VM

2. Використання Apache HttpClient

Додайте Apache HttpClient до залежностей від Maven або Gradle.

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

Використовувати HttpComponentsClientHttpRequestFactoryяк RequestFactory для RestTemplate. Найпростіший спосіб зробити це:

RestTemplate restTemplate = new RestTemplate();

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Увімкніть DEBUG у вашому application.propertiesфайлі (якщо ви використовуєте Spring Boot)

logging.level.org.apache.http=DEBUG

Якщо ви використовуєте Spring Boot, вам потрібно буде переконатися, що ви створили рамку ведення журналу, наприклад, використовуючи залежність ведучого завантаження-стартера, яка включає spring-boot-starter-logging.

3. Використовуйте перехоплювач

Я дам вам ознайомитись з пропозиціями, зустрічними пропозиціями та ґетчами в інших відповідях та коментарях та вирішити для себе, чи хочете ви піти цим шляхом.

4. Введіть URL-адресу та статус відповіді без тіла

Хоча це не відповідає заявленим вимогам реєстрації тіла, це швидкий і простий спосіб розпочати реєстрацію REST-дзвінків. Він відображає повну URL-адресу та статус відповіді.

Просто додайте у application.propertiesфайл наступний рядок (якщо ви використовуєте Spring Boot та припускаєте, що ви використовуєте залежність стартера весняного завантаження, яка включає spring-boot-starter-logging)

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

Вихід буде виглядати приблизно так:

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
№4 - найпростіший спосіб налагодження.
Yubaraj

1
№2 працював на мене. Він записує тіло запиту. Дякую!
caglar

1
Я знайшов №3 простий спосіб зробити це, коли я прийшов до цього питання.
Білл Нейлор

12

Крім журналу HttpClient, описаного в іншій відповіді , ви також можете ввести ClientHttpRequestIntercepttor, який зчитує тіло запиту та відповідь та записує його в журнал. Ви можете зробити це, якщо інші матеріали також використовують HttpClient або якщо ви бажаєте користувальницький формат журналу. Застереження: ви хочете надати RestTemplate BufferingClientHttpRequestFactory таким чином, щоб ви могли прочитати відповідь двічі.


12

Як зазначено в інших відповідях, органу реагування потрібно спеціальне лікування, щоб його можна було прочитати повторно (за замовчуванням його вміст споживається під час першого читання).

Замість того, щоб використовувати BufferingClientHttpRequestFactoryпри налаштуванні запиту, перехоплювач сам може завернути відповідь і переконатися, що вміст зберігається і може бути повторно прочитаний (реєстратором, а також споживачем відповіді):

Мій перехоплювач, який

  • буферизує тіло відповіді за допомогою обгортки
  • колоди більш компактним способом
  • також записує ідентифікатор коду стану (наприклад, 201 Створено)
  • включає в себе порядковий номер запиту, що дозволяє легко відрізняти паралельні записи журналу від декількох потоків

Код:

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

Конфігурація:

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

Приклад журналу:

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
Цей працює з весняною версією 2019 року, зберігаючи тіло неушкодженим.
Удо відбувся

1
Працює навесні 2.1.10 :) Дякую
Молер

8

застосування.властивості

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

application.yml

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

8

Це може бути не правильним способом зробити це, але я думаю, що це найпростіший підхід до друку запитів та відповідей, не надто заповнюючи журнали.

Додавши нижче 2 рядки application.properties записує всі запити та відповіді 1-й рядок для того, щоб записувати запити та 2-й рядок для реєстрації відповідей.

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

Реєстрація відповідей для мене не працює. Він просто записує код коду. Чи повинен він реєструвати корисне навантаження?
badera

Клас HttpEntityMethodProcessor (v5.1.8) нічого не записує.
Кріс

6

Якщо припустити , що RestTemplateбуде налаштований використовувати HttpClient 4.x, ви можете прочитати на HttpClient - х лісозаготівельної документації тут . Лісоруби відрізняються від визначених в інших відповідях.

Конфігурація журналу для HttpClient 3.x доступна тут .


4

Тому багато відповідей вимагають змін кодування та індивідуальних класів, і це дійсно не потрібно. Зробіть налагоджувальний проксі, наприклад fiddler, і встановіть ваше середовище java для використання проксі-сервера в командному рядку (-Dhttp.proxyHost та -Dhttp.proxyPort), тоді запустіть fiddler, і ви зможете побачити запити та відповіді в повному обсязі. Також є безліч допоміжних переваг, таких як можливість познайомитися з результатами та відповідями до і після того, як вони будуть відправлені на виконання експериментів, перш ніж здійснити модифікацію сервера.

Останній біт проблеми, який може виникнути, - це якщо вам потрібно використовувати HTTPS, вам потрібно буде експортувати SSL-файл із скрипту та імпортувати його у підказку Java-клавіші (cacerts): за замовчуванням пароль для зберігання ключів Java зазвичай "зміниться".


1
Це працювало для мене за допомогою intellij та регулярної установки скрипки. Я відредагував конфігурацію запуску і встановив параметри VM на -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888.
JD

Дякую! Це досить елегантне рішення порівняно з написанням власного перехоплювача.
Кріс

3

Щоб увійти до Logback за допомогою Apache HttpClient:

Вам потрібен Apache HttpClient в класі:

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

Налаштуйте свій доступ RestTemplateдо HttpClient:

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

Щоб увімкнути запити та відповіді, додайте до файлу конфігурації Logback:

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

Або ввійти ще більше:

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

Який файл конфігурації для зворотного зв'язку?
G_V

1
@G_V logback.xml або logback-test.xml для тестів.
holmis83

Він також працює з org.apache.http.wire=DEBUGвашим application.propertiesзараз
G_V

@G_V, якщо ви використовуєте Spring-Boot. Моя відповідь працює без завантаження.
holmis83

2

Трюк налаштування вашого RestTemplateна BufferingClientHttpRequestFactoryне працює, якщо ви використовуєте будь-який ClientHttpRequestInterceptor, який ви будете робити, якщо ви намагаєтесь увійти через перехоплювачі. Це пов'язано з тим, як InterceptingHttpAccessor(які RestTemplateпідкласи) працює.

Короткий RestTemplateогляд історії… просто використовуйте цей клас замість (зауважте, для цього використовується API журналу SLF4J, відредагуйте за потребою):

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

Я погоджуюся, що нерозумно, що для цього потрібно стільки роботи.


2

Додаючи до обговорення вище, це відображає лише щасливі сценарії. ймовірно, ви не зможете зафіксувати відповідь, якщо з’явиться помилка .

У цьому випадку плюс усі випадки вище, ви повинні перекрити DefaultResponseErrorHandler і встановити його як нижче

restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());

2

Як не дивно, жодне з цих рішень не працює, оскільки RestTemplate, схоже, не повертає відповідь на деякі клієнтські та серверні помилки 500x. У такому випадку ви також ввійдете в журнал, застосувавши ResponseErrorHandler наступним чином. Ось проект коду, але ви розумієте:

Ви можете встановити той самий перехоплювач, як і обробник помилок:

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

І перехоплення реалізує обидва інтерфейси:

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

Що робити, якщо тіло є багаточастинними / форм-даними, чи є простий спосіб відфільтрувати двійкові дані (вміст файлу) з журналу?
Лука

1

Як зазначав @MilacH, в реалізації є помилка. Якщо statusCode> 400 повернуто, від перехоплювачів викидається IOException, оскільки помилка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=================================================");
    }

}

0

Найкраще рішення, просто додайте залежність:

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

Він містить клас LoggingRequestInterceptor, який ви можете додати таким чином до свого RestTemplate:

інтегруйте цю утиліту, додавши її як перехоплювач до весняного RestTemplate таким чином:

restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build());

і додати реалізацію slf4j до своєї рамки, як log4j.

або безпосередньо використовувати "Zg2proRestTemplate" . "Найкраща відповідь" від @PaulSabou виглядає так, оскільки httpclient і всі вкладки apache.http не обов'язково завантажуються під час використання пружинної RestTemplate.


яка випущена версія?
popalka

випущена версія зараз становить 0,2
Мойсей Мейєр

1
простота використання чудова, але у неї відсутні заголовки
WrRaThY

додатково: всі корисні методи в LoggingRequestInterceptor є приватними, що є проблемою, коли мова йде про розширення (може бути захищено)
WrRaThY

на жаль, я не можу редагувати коментарі через 5 хвилин. Все , що вам потрібно зробити , щоб увійти заголовки це: log("Headers: {}", request.headers)в LoggingRequestInterceptor:traceRequestі log("Headers: {}", response.headers)в LoggingRequestInterceptor:logResponse. Можливо, ви захочете подумати над тим, щоб додати кілька прапорів для реєстрації заголовків і корпусу. Також - ви можете перевірити тип вмісту тіла для ведення журналу (наприклад, лише журнал application / json *). Це також слід налаштовувати. загалом, за допомогою цих маленьких твіст у вас буде приємна бібліотека для розповсюдження. хороша робота :)
WrRaThY

0

Хотів додати і мою реалізацію цього. Прошу вибачення за всі зниклі напівколони, про це написано в Groovy.

Мені потрібно було щось більш налаштоване, ніж надана відповідь. Ось шаблон шаблону для відпочинку, який є дуже гнучким і записуватиме все, як шукає ОП.

Спеціальний клас перехоплювача журналу:

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

Визначення квасолі шаблону відпочинку:

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

Впровадження:

@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 дають занадто нечитабельні журнали, тому я використовую журнал для реєстрації програми Servlet та RestTemplate req / resp для входу

build.gradle

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

застосування.властивості

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

Зв'язаний з відповіддю за допомогою ClientHttpInterceptor, я знайшов спосіб утримати всю відповідь без фабрики буферизації. Просто збережіть вхідний потік тіла відповіді всередині байтового масиву, використовуючи метод утиліти, який буде копіювати цей масив з тіла, але важливо, оточити цей метод за допомогою try catch, тому що він порушиться, якщо відповідь буде порожній (що є причиною винятку доступу до ресурсів) та у програмі просто створити порожній байтовий масив, а потім просто створити анонімний внутрішній клас ClientHttpResponse, використовуючи цей масив та інші параметри вихідної відповіді. Тоді ви можете повернути цей новий об’єкт ClientHttpResponse до ланцюжка виконання шаблону решти шаблону, і ви можете занотувати відповідь, використовуючи попередньо збережений масив байтів тіла. Таким чином ви уникнете споживання InputStream в реальній відповіді і зможете використовувати відповідь шаблону відпочинку як є. Примітка,


-2

мій конфігуратор реєстратора використовував xml

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

тоді ви отримаєте щось на кшталт нижче:

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]

через HttpMessageConverterExtractor.java:92, вам потрібно продовжувати налагоджувати, і в моєму випадку я отримав це:

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

і це:

outputMessage.getBody().flush();

outputMessage.getBody () містить повідомлення, яке http (тип повідомлення) надсилає


реєстрація слідів може бути надто багатослівною ... що робити, якщо є тисячі запитів в секунду ??
Гервасіо Емі
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.