Spring RestTemplate - как включить полную отладку / запись запросов / ответов? - PullRequest
175 голосов
/ 31 октября 2011

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

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 просто не работает (в некоторых случаях).

Ответы [ 24 ]

172 голосов
/ 08 октября 2015

Просто для завершения примера с полной реализацией 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 требуется, поскольку мы хотим использовать тело ответа как в перехватчике, так и для исходного кода вызова. Реализация по умолчанию позволяет прочитать тело ответа только один раз.

100 голосов
/ 23 августа 2016

в 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, который, кажется, содержит всю необходимую информацию

76 голосов
/ 25 марта 2014

Расширение ответа @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());
27 голосов
/ 02 марта 2016

Ни один из этих ответов на самом деле не решает 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);
24 голосов
/ 01 ноября 2011

Я наконец нашел способ сделать это правильно. Большая часть решения исходит от Как мне настроить Spring и SLF4J, чтобы я мог получать логи?

Кажется, нужно сделать две вещи:

  1. Добавьте следующую строку в log4j.properties: log4j.logger.httpclient.wire=DEBUG
  2. Убедитесь, что spring не игнорирует вашу конфигурацию регистрации

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

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

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

23 голосов
/ 01 февраля 2017

Решение, данное xenoterracide для использования

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());
22 голосов
/ 04 июля 2016

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

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

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

19 голосов
/ 01 ноября 2017

Вы можете использовать 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()

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

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

18 голосов
/ 24 ноября 2017

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

Опция 1. Открыть ведение журнала отладки.

Настройка RestTemplate

  • По умолчанию RestTemplate полагается настандартные средства JDK для установки HTTP-соединений.Вы можете переключиться на использование другой библиотеки HTTP, такой как Apache HttpComponents

    @ public Bean 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:

    logging: level: org.apache.http: DEBUG

10 голосов
/ 25 февраля 2014

Помимо ведения журнала HttpClient, описанного в другом ответе , вы также можете ввести ClientHttpRequestInterceptor, который читает тело запроса и ответа и регистрирует его. Возможно, вы захотите сделать это, если другие вещи также используют HttpClient, или если вы хотите пользовательский формат регистрации. Предостережение: вам нужно дать RestTemplate объект BufferingClientHttpRequestFactory, чтобы вы могли прочитать ответ дважды.

...