Spring RestTemplate - как включить полную отладку / запись запросов / ответов?


221

Я уже некоторое время использую Spring RestTemplate и постоянно бьюсь о стену, когда пытаюсь отлаживать ее запросы и ответы. Я в основном ищу то же самое, что и при использовании curl с включенной опцией «verbose». Например :

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

Будет отображать как отправленные данные, так и полученные данные (включая заголовки, куки и т. Д.).

Я проверил некоторые похожие сообщения, такие как: Как мне регистрировать ответы в Spring RestTemplate? но мне не удалось решить эту проблему.

Один из способов сделать это состоит в том, чтобы фактически изменить исходный код RestTemplate и добавить туда несколько дополнительных операторов регистрации, но я бы нашел этот подход действительно последним средством. Должен быть какой-то способ сказать Spring Web Client / RestTemplate, чтобы все регистрировалось более дружественным способом.

Моя цель - сделать это с помощью кода вроде:

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

и затем получить тот же тип отладочной информации (как я получаю с помощью curl) в файле журнала или в консоли. Я считаю, что это было бы чрезвычайно полезно для любого, кто использует Spring RestTemplate и имеет проблемы. Использование curl для отладки ваших проблем RestTemplate просто не работает (в некоторых случаях).


31
Предупреждение всем, кто читает в 2018 году: на это нет простого ответа!
Давидфрансис

3
Самый простой способ - это использовать точку останова в методе write (...) класса AbstractHttpMessageConverter, есть объект outputMessage, где вы можете видеть данные. PS Вы можете скопировать значение и затем отформатировать его с помощью онлайн-форматера.
Сергей Чепурнов

1
Похоже, это должно быть легко сделать весной, но, судя по ответам здесь - не так. Итак, еще одно решение - полностью обойти Spring и использовать такой инструмент, как Fiddler, для захвата запроса / ответа.
michaelok

прочитайте ответ на этот вопрос по следующей ссылке: spring-resttemplate-how-to-enable-full-debugging-logging-of-
messages-answers

Июль 2019 года. Поскольку до сих пор нет простого решения этого вопроса, я постарался дать резюме остальных 24 ответов (пока) и их комментарии и обсуждения в своем собственном ответе ниже . Надеюсь, поможет.
Крис

Ответы:


208

Просто для завершения примера с полной реализацией 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с помощью a BufferingClientHttpRequestFactoryи LoggingRequestInterceptor:

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

BufferingClientHttpRequestFactoryТребуется , как мы хотим , чтобы использовать тело ответа как в перехватчик и для исходного кода вызова. Реализация по умолчанию позволяет прочитать тело ответа только один раз.


28
Это не верно. Если вы прочитаете поток, код приложения не сможет прочитать ответ.
Джеймс Уоткинс

28
мы дали RestTemplate объект BufferingClientHttpRequestFactory, чтобы мы могли прочитать ответ дважды.
Софиене Загдуди

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

130

в 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
Это была самая простая вещь, которая сделала то, что я хотел. Я настоятельно рекомендую включить это в принятый ответ.
Михаэлавила

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
Ортомала Локни

22
RestTemplate не использует эти классы Apache по умолчанию, как указано @OrtomalaLokni, поэтому вы должны также указать, как их использовать, в дополнение к тому, как печатать отладку, когда они используются.
Капитан Мэн

Я получаю вот так:http-outgoing-0 << "[0x1f][0x8b][0x8][0x0][0x0][0x0][0x0][0x0]
Партха Саратхи Гош

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 версии.
Гьян

3
он не отвечает на вопрос 'logging response', но вместо этого оставляет комментарий // do logging.
Цзян Я.Д.

1
Ведение журнала было легко, но это работает только для запросов, я не вижу тела ответов, предположим, у меня есть объект ответа, но чтение его потока не является хорошей идеей.
Павел Недоба

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

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

32

Лучше всего добавить logging.level.org.springframework.web.client.RestTemplate=DEBUGвapplication.properties файл.

Другие решения, такие как настройка log4j.logger.httpclient.wire, не всегда будут работать, потому что они предполагают, что вы используете log4jApache.HttpClient , что не всегда верно.

Обратите внимание, что этот синтаксис будет работать только в последних версиях Spring Boot.


5
Это не регистрация тела запроса и ответа, а только URL и тип запроса (spring-web-4.2.6)
dve

1
Вы правы, это не wireлогирование, оно включает в себя только важную информацию, такую ​​как URL, повторный отсчет кода, параметры POST и т. Д.
gamliela

1
что вам действительно нужно, так это stackoverflow.com/a/39109538/206466
xenoterracide

Это хорошо, но тело ответа не было видно!
Sunleo

Brilliant. Хотя он не печатает тело ответа, он все равно очень полезен. Спасибо.
Крис

30

Ни один из этих ответов на самом деле не решает 100% проблемы. mjj1409 получает большую часть этого, но удобно избегает проблемы регистрации ответа, которая требует немного больше работы. Пол Сабу предлагает решение, которое кажется реалистичным, но не предоставляет достаточно подробностей для его реализации (и оно не сработало для меня). Софиен получила протоколирование, но с критической проблемой: ответ больше не читается, потому что входной поток уже использован!

Я рекомендую использовать 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 на вашем пути к классам, вы можете найти простую реализацию здесь:

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()
Anand Rockzz

4
Но это частный пакет. Вы поместили свой LoggingRequestInterceptor в пакет 'org.springframework.http.client'?
zbstof

2
о чем asyncRestTemplate? ListenableFutureКогда вы перехватываете, потребуется вернуть a , который невозможно изменить при обратном BufferingClientHttpResponseWrapperвызове.
Омер Фарук Алмалы

@ ÖmerFarukAlmalı В этом случае вам нужно будет использовать цепочку или преобразование в зависимости от версии используемой вами гуавы. См .: stackoverflow.com/questions/8191891/…
Джеймс Уоткинс,

30

Вы можете использовать spring-rest-template-logger для регистрации 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 на уровне отладки.

ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: Я написал эту библиотеку.


Почему этот ответ опущен? Это помогло мне. Спасибо, Марк Хобсон.
Раффаэль Бечара Рамех

3
Рад, что это помогло @RaffaelBecharaRameh. Первоначально он был отклонен, потому что я не вставлял инструкции из связанного проекта. Не стесняйтесь, если вы нашли это полезным!
Марк Хобсон

Вы поддерживаете через Gradle?
BlackHatSamurai

1
@BlackHatSamurai spring-rest-template-logger - это обычный артефакт 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. Убедитесь, что Spring не игнорирует вашу конфигурацию регистрации

Вторая проблема возникает в основном в среде Spring, где используется slf4j (как это было в моем случае). Таким образом, при использовании slf4j убедитесь, что следующие две вещи происходят:

  1. В вашем пути к классам нет библиотеки регистрации общего доступа: это можно сделать, добавив дескрипторы исключения в ваш pom:

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


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

Это не работает для меня тоже. Я даже попытался установить корневой логгер в режим отладки, но ничего не вышло.
Джеймс Уоткинс

«httpclient.wire.content» и «httpclient.wire.header» являются именами регистраторов из среды Axis2. Они могут использоваться для регистрации, например, запросов SOAP в проекте Spring, если они выполняются с использованием Axis2.
Латспелл

11
httpclient.wireна самом деле из библиотеки Apache HttpComponents HttpClient (см. 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 builder) {RestTemplate restTemplate = builder.build (); вернуть restTemplate; }

Настроить ведение журнала

  • application.yml

    ведение журнала: level: 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:

    logging: level: 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 (new LoggingRestTemplate ())); }
кинголег

Также обратите внимание, что new InputStreamReader (responseWrapper.getBody (), StandardCharsets.UTF_8)); может выдать ошибку, если «другой конец» вернул ошибку. Вы можете разместить его в блоке try.
PeterS

16

---- июль 2019 года ----

(используя Spring Boot)

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

1. Использование Fiddler в качестве http-прокси

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

Установите и запустите Fiddler

а потом

добавить -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888в ваши параметры виртуальной машины

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, которая включает spring-boot-starter-logging .

3. Используйте перехватчик

Я позволю вам прочитать предложения, встречные предложения и ошибки в других ответах и ​​комментариях и решить для себя, хотите ли вы пойти по этому пути.

4. Записать URL и статус ответа без тела

Хотя это не отвечает заявленным требованиям регистрации тела, это быстрый и простой способ начать регистрацию ваших вызовов REST. Он отображает полный URL-адрес и статус ответа.

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

logging.level.org.springframework.web.client.RestTemplate = ОТЛАДКА

Вывод будет выглядеть примерно так:

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 - самый простой способ отладки.
Юбарадж

1
№ 2 работал на меня. Он регистрирует тело запроса. Спасибо!
Caglar

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

12

Помимо ведения журнала HttpClient, описанного в другом ответе , вы также можете ввести ClientHttpRequestInterceptor, который читает тело запроса и ответ и регистрирует его. Возможно, вы захотите сделать это, если другие вещи также используют 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 Spring, сохраняя тело в целости и сохранности.
Удо

1
Работает на весне 2.1.10 :) Спасибо
Moler


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

Регистрация ответов не работает для меня. Он просто регистрирует код состояния. Должен ли он регистрировать полезную нагрузку?
Бадера

Класс HttpEntityMethodProcessor (v5.1.8) ничего не регистрирует.
Крис

6

Предполагая, RestTemplateчто настроено использование HttpClient 4.x, вы можете прочитать документацию по протоколированию HttpClient здесь . Регистраторы отличаются от тех, которые указаны в других ответах.

Конфигурация регистрации для HttpClient 3.x доступна здесь .


4

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

Последняя проблема, которая может возникнуть, заключается в том, что если вы должны использовать HTTPS, вам нужно будет экспортировать сертификат SSL из fiddler и импортировать его в подсказку хранилища ключей java (cacerts): пароль хранилища ключей java по умолчанию обычно "changeit".


1
Это сработало для меня, используя intellij и обычную установку скрипки. Я отредактировал конфигурацию запуска и установил параметры виртуальной машины в -DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888.
JD

Спасибо! Это довольно элегантное решение по сравнению с написанием вашего собственного перехватчика.
Крис

3

Для входа в Logback с помощью Apache HttpClient:

Вам нужен Apache HttpClient в classpath:

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

Какой файл конфигурации logback?
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, похоже, не возвращает ответ на некоторые ошибки клиента и сервера в 500 раз. В этом случае вы также будете регистрировать их, реализовав 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);
    }
}

Что если body является multipart / form-data, есть ли простой способ отфильтровать двоичные данные (содержимое файла) из журнала?
Люк

1

Как указал @MilacH, в реализации есть ошибка. Если statusCode> 400 возвращается, IOException генерируется, так как errorHandler не вызывается из перехватчиков. Исключение можно игнорировать, а затем снова перехватить в методе обработчика.

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:

интегрируйте эту утилиту, добавив ее в качестве перехватчика в Spring RestTemplate, следующим образом:

restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build());

и добавьте реализацию slf4j в ваш фреймворк, например, log4j.

или напрямую используйте "Zg2proRestTemplate" . «Лучший ответ» @PaulSabou выглядит так, поскольку httpclient и все библиотеки apache.http не обязательно загружаются при использовании пружинного RestTemplate.


какая выпущенная версия?
попалка

выпущенная версия теперь 0.2
Моисей Мейер

1
простота использования велика, но ей не хватает заголовков
WrRaThY

дополнительно: все полезные методы в LoggingRequestInterceptor являются закрытыми, что является проблемой, когда речь идет о расширении (может быть защищено)
WrRaThY

к сожалению, я не могу редактировать комментарии через 5 минут. Все, что вам нужно сделать для регистрации заголовков, это: log("Headers: {}", request.headers)внутри LoggingRequestInterceptor:traceRequestи log("Headers: {}", response.headers)внутри LoggingRequestInterceptor:logResponse. Вы можете подумать о добавлении некоторых флагов для регистрации заголовков и тела. Кроме того - вы можете проверить тип содержимого тела для регистрации (например, log only application / json *). Это также должно быть настраиваемым. в общем, с этими небольшими изменениями у вас будет хорошая библиотека для распространения. хорошая работа :)
WrRaThY

0

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

Мне нужно что-то более настраиваемое, чем принятый ответ. Вот бин шаблона отдыха, который очень гибок и регистрирует все, как ищет OP.

Пользовательский класс перехватчика регистрации:

import org.springframework.http.HttpRequest
import org.springframework.http.client.ClientHttpRequestExecution
import org.springframework.http.client.ClientHttpRequestInterceptor
import org.springframework.http.client.ClientHttpResponse
import org.springframework.util.StreamUtils

import java.nio.charset.Charset

class HttpLoggingInterceptor implements ClientHttpRequestInterceptor {

    private final static Logger log = LoggerFactory.getLogger(HttpLoggingInterceptor.class)

    @Override
    ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        logRequest(request, body)
        ClientHttpResponse response = execution.execute(request, body)
        logResponse(response)
        return response
    }

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

    private void logResponse(ClientHttpResponse response) throws IOException {
        if (log.isDebugEnabled()) {
            log.debug("============================response begin==========================================")
            log.debug("Status code  : {}", response.getStatusCode())
            log.debug("Status text  : {}", response.getStatusText())
            log.debug("Headers      : {}", response.getHeaders())
            log.debug("Response body: {}", StreamUtils.copyToString(response.getBody(), Charset.defaultCharset()))
            log.debug("=======================response end=================================================")
        }
    }
}

Определение шаблона компонента Rest:

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

application.properties

logging.level.org.zalando.logbook:TRACE

RestTemplate

@Configuration
public class RestTemplateConfig {

@Autowired
private LogbookHttpRequestInterceptor logbookHttpRequestInterceptor;

@Autowired
private LogbookHttpResponseInterceptor logbookHttpResponseInterceptor;

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

class MyRequestFactorySupplier implements Supplier<ClientHttpRequestFactory> {

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

}
}

-1

Что касается ответа с использованием ClientHttpInterceptor, я нашел способ сохранить весь ответ без буферизации фабрик. Просто сохраните входной поток тела ответа внутри байтового массива, используя некоторый метод utils, который скопирует этот массив из тела, но важно, окружите этот метод try catch, потому что он сломается, если ответ пуст (что является причиной исключения доступа к ресурсам), и в catch просто создайте пустой байтовый массив, а затем просто создайте анонимный внутренний класс ClientHttpResponse, используя этот массив и другие параметры из исходного ответа. Затем вы можете вернуть этот новый объект ClientHttpResponse в цепочку выполнения остальных шаблонов и зарегистрировать ответ, используя байтовый массив тела, который был ранее сохранен. Таким образом, вы не будете использовать InputStream в реальном ответе и сможете использовать ответ Rest Template как есть. Заметка,


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