Перейти к основному содержимому

Ведение журнала запросов/ответов Spring RestTemplate

· 5 мин. чтения

1. Обзор

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

К сожалению, Spring Boot не предоставляет простого способа проверить или зарегистрировать простое тело ответа JSON.

Мы собираемся изучить несколько методов регистрации либо заголовков HTTP, либо, что наиболее интересно, тела HTTP.

Примечание . Spring RestTemplate будет устаревшим и будет заменен WebClient . Вы можете найти аналогичную статью с использованием WebClient здесь: Logging Spring WebClient Calls .

2. Базовое ведение журнала с помощью RestTemplate

Приступим к настройке логгера RestTemplate в файле application.properties :

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

В результате мы можем видеть только основную информацию , такую как URL-адрес запроса, метод, тело и статус ответа:

o.s.w.c.RestTemplate - HTTP POST http://localhost:8082/spring-rest/persons
o.s.w.c.RestTemplate - Accept=[text/plain, application/json, application/*+json, */*]
o.s.w.c.RestTemplate - Writing [my request body] with org.springframework.http.converter.StringHttpMessageConverter
o.s.w.c.RestTemplate - Response 200 OK

Однако здесь не записывается тело ответа , что очень жаль, потому что это самая интересная часть.

Чтобы решить эту проблему, мы выберем Apache HttpClient или перехватчик Spring.

3. Регистрация заголовков/тела с помощью Apache HttpClient

Во- первых, мы должны заставить RestTemplate использовать реализацию Apache HttpClient .

Нам понадобится зависимость Maven :

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

При создании экземпляра RestTemplate мы должны сообщить ему, что используем Apache HttpClient :

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

Затем настроим клиентский логгер в файле application.properties :

logging.level.org.apache.http=DEBUG
logging.level.httpclient.wire=DEBUG

Теперь мы можем видеть как заголовки запроса/ответа, так и тело:

o.a.http.headers - http-outgoing-0 >> POST /spring-rest/persons HTTP/1.1
o.a.http.headers - http-outgoing-0 >> Accept: text/plain, application/json, application/*+json, */*
// ... more request headers
o.a.http.headers - http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.9 (Java/1.8.0_171)
o.a.http.headers - http-outgoing-0 >> Accept-Encoding: gzip,deflate
org.apache.http.wire - http-outgoing-0 >> "POST /spring-rest/persons HTTP/1.1[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Accept: text/plain, application/json, application/*+json, */*[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "Content-Type: text/plain;charset=ISO-8859-1[\r][\n]"
// ... more request headers
org.apache.http.wire - http-outgoing-0 >> "[\r][\n]"
org.apache.http.wire - http-outgoing-0 >> "my request body"
org.apache.http.wire - http-outgoing-0 << "HTTP/1.1 200 [\r][\n]"
org.apache.http.wire - http-outgoing-0 << "Content-Type: application/json[\r][\n]"
// ... more response headers
org.apache.http.wire - http-outgoing-0 << "Connection: keep-alive[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "21[\r][\n]"
org.apache.http.wire - http-outgoing-0 << "["Lucie","Jackie","Danesh","Tao"][\r][\n]"

Однако эти журналы подробны и неудобны для отладки .

Мы увидим, как решить эту проблему в следующей главе.

4. Запись тела с помощью перехватчика RestTemplate

В качестве другого решения мы можем настроить перехватчики для RestTemplate .

4.1. Реализация перехватчика регистрации

Во-первых, давайте создадим новый LoggingInterceptor для настройки наших журналов . Этот перехватчик регистрирует тело запроса как простой массив байтов. Однако для ответа нам нужно прочитать весь поток тела:

public class LoggingInterceptor implements ClientHttpRequestInterceptor {

static Logger LOGGER = LoggerFactory.getLogger(LoggingInterceptor.class);

@Override
public ClientHttpResponse intercept(
HttpRequest req, byte[] reqBody, ClientHttpRequestExecution ex) throws IOException {
LOGGER.debug("Request body: {}", new String(reqBody, StandardCharsets.UTF_8));
ClientHttpResponse response = ex.execute(req, reqBody);
InputStreamReader isr = new InputStreamReader(
response.getBody(), StandardCharsets.UTF_8);
String body = new BufferedReader(isr).lines()
.collect(Collectors.joining("\n"));
LOGGER.debug("Response body: {}", body);
return response;
}
}

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

4.2. Использование перехватчика с RestTemplate

Теперь мы должны решить проблему потоковой передачи: поскольку перехватчик потребляет поток ответа, наше клиентское приложение увидит пустое тело ответа .

Чтобы избежать этого, мы должны использовать BufferingClientHttpRequestFactory : он буферизует содержимое потока в память. Таким образом, он может быть прочитан дважды: один раз нашим перехватчиком и второй раз нашим клиентским приложением:

ClientHttpRequestFactory factory = 
new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
RestTemplate restTemplate = new RestTemplate(factory);

Однако использование этой фабрики влечет за собой недостаток производительности , который мы опишем в следующем подразделе.

Затем мы можем добавить наш перехватчик журналирования в экземпляр RestTemplate — мы добавим его после существующих перехватчиков, если они есть:

List<ClientHttpRequestInterceptor> interceptors = restTemplate.getInterceptors();
if (CollectionUtils.isEmpty(interceptors)) {
interceptors = new ArrayList<>();
}
interceptors.add(new LoggingInterceptor());
restTemplate.setInterceptors(interceptors);

В результате в логах присутствует только необходимая информация:

c.b.r.l.LoggingInterceptor - Request body: my request body
c.b.r.l.LoggingInterceptor - Response body: ["Lucie","Jackie","Danesh","Tao"]

4.3. Недостаток перехватчика RestTemplate

Как упоминалось ранее, использование BufferingClientHttpRequestFactory имеет серьезный недостаток: оно сводит на нет преимущества потоковой передачи. Как следствие, загрузка всех данных тела в память может привести к проблемам с производительностью нашего приложения. Хуже того, это может привести к OutOfMemoryError .

Чтобы предотвратить это, один из возможных вариантов — предположить, что эти подробные журналы будут отключены при увеличении объема данных, что обычно происходит в рабочей среде. Например, мы можем использовать буферизованный экземпляр RestTemplate , только если `` в нашем регистраторе включен уровень DEBUG :

RestTemplate restTemplate = null;
if (LOGGER.isDebugEnabled()) {
ClientHttpRequestFactory factory
= new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
restTemplate = new RestTemplate(factory);
} else {
restTemplate = new RestTemplate();
}

Точно так же мы обеспечим, чтобы наш перехватчик считывал ответ только тогда, когда включено ведение журнала DEBUG :

if (LOGGER.isDebugEnabled()) {
InputStreamReader isr = new InputStreamReader(response.getBody(), StandardCharsets.UTF_8);
String body = new BufferedReader(isr)
.lines()
.collect(Collectors.joining("\n"));
LOGGER.debug("Response body: {}", body);
}

5. Вывод

Ведение журнала запросов/ответов RestTemplate не является простым делом, поскольку Spring Boot не включает его в готовом виде.

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

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

Как всегда, исходный код этой статьи доступен на GitHub в тестовой папке. В примере используется RestTemplate в реальном тесте для конечной точки REST, определенной в том же проекте.