Spring RestTemplate-如何启用请求/响应的完整调试/日志记录?


220

我使用Spring RestTemplate已经有一段时间了,当我尝试调试它的请求和响应时,我总是碰壁。我基本上希望看到的东西与打开“ verbose”选项时使用curl时看到的东西相同。例如 :

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

将同时显示已发送的数据和已接收的数据(包括标题,cookie等)。

我已经检查了一些相关的帖子,例如: 如何在Spring RestTemplate中记录响应? 但是我还没有解决这个问题。

一种方法是实际更改RestTemplate源代码并在其中添加一些额外的日志记录语句,但是我发现这种方法确实是不得已的事情。应该有某种方式告诉Spring Web Client / RestTemplate以更友好的方式记录所有内容。

我的目标是能够使用以下代码执行此操作:

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

然后在日志文件或控制台中获取相同类型的调试信息(与curl相同)。我相信这对于使用Spring RestTemplate并有问题的任何人都将非常有用。使用curl调试RestTemplate问题根本无法正常工作(在某些情况下)。


30
警告任何在2018年阅读的人:没有一个简单的答案!
davidfrancis

3
最简单的方法是在AbstractHttpMessageConverter类的write(...)方法中使用断点,这里有一个outputMessage对象,您可以在其中查看数据。PS您可以复制该值,然后使用在线格式化程序对其进行格式化。
谢尔盖·切普尔诺夫

1
在Spring看来这应该很容易做到,但是从这里的答案来看-并非如此。因此,另一个解决方案是完全绕过Spring,并使用Fiddler之类的工具来捕获请求/响应。
michaelok '19


2019年7月:由于此问题仍然没有简单的解决方案,因此我尝试在下面的我自己的答案中总结其他24个答案(到目前为止)以及他们的评论和讨论。希望能帮助到你。
克里斯,

Answers:


206

只是为了完成示例ClientHttpRequestInterceptor以跟踪请求和响应的完整实现:

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

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

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {

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

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

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

    private void traceResponse(ClientHttpResponse response) throws IOException {
        StringBuilder inputStringBuilder = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), "UTF-8"));
        String line = bufferedReader.readLine();
        while (line != null) {
            inputStringBuilder.append(line);
            inputStringBuilder.append('\n');
            line = bufferedReader.readLine();
        }
        log.info("============================response begin==========================================");
        log.debug("Status code  : {}", response.getStatusCode());
        log.debug("Status text  : {}", response.getStatusText());
        log.debug("Headers      : {}", response.getHeaders());
        log.debug("Response body: {}", inputStringBuilder.toString());
        log.info("=======================response end=================================================");
    }

}

然后RestTemplate使用BufferingClientHttpRequestFactory和实例化LoggingRequestInterceptor

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

BufferingClientHttpRequestFactory因为我们要在拦截器和初始调用代码中都使用响应主体,所以这是必需的。默认实现只允许读取一次响应主体。


27
错了 如果您读取流,则应用程序代码将无法读取响应。
詹姆斯·沃特金斯

28
我们为RestTemplate提供了一个BufferingClientHttpRequestFactory,以便我们可以读取两次响应。
sofiene zaghdoudi '16

16
我们已经使用这种技术大约3个月了。它仅与配置BufferingClientHttpResponseWrapper为@sofienezaghdoudi所暗示的RestTemplate一起使用。但是,当在使用spring的mockServer框架的测试中使用时,它不起作用,因为它将MockRestServiceServer.createServer(restTemplate)RequestFactory覆盖为InterceptingClientHttpRequestFactory
RubesMN

8
技术好,实现错误。404情况下,response.getBody()抛出IOException->您永远都不会丢失日志,甚至更糟的是,它将在您的其他代码中成为ResourceAccessException,而不是RestClientResponseException
MilacH

5
谢谢回复。但是,拥有多个“ log.debug”是一个坏习惯,因为它可能会散布在许多其他日志中。最好使用单个log.debug指令,以确保所有内容都在同一地方
user2447161

127

在Spring Boot中,您可以通过在属性(或其他12因子方法)中进行设置来获取完整的请求/响应

logging.level.org.apache.http=DEBUG

这个输出

-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connecting to localhost/127.0.0.1:41827
-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connection established 127.0.0.1:39546<->127.0.0.1:41827
-DEBUG o.a.http.impl.execchain.MainClientExec   : Executing request POST /v0/users HTTP/1.1
-DEBUG o.a.http.impl.execchain.MainClientExec   : Target auth state: UNCHALLENGED
-DEBUG o.a.http.impl.execchain.MainClientExec   : Proxy auth state: UNCHALLENGED
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> POST /v0/users HTTP/1.1
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Type: application/json;charset=UTF-8
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Length: 56
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Host: localhost:41827
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Connection: Keep-Alive
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Accept-Encoding: gzip,deflate
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "POST /v0/users HTTP/1.1[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Type: application/json;charset=UTF-8[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Length: 56[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Host: localhost:41827[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "{"id":null,"email":"xenoterracide@gmail.com","new":true}"

和回应

-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connecting to localhost/127.0.0.1:41827
-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connection established 127.0.0.1:39546<->127.0.0.1:41827
-DEBUG o.a.http.impl.execchain.MainClientExec   : Executing request POST /v0/users HTTP/1.1
-DEBUG o.a.http.impl.execchain.MainClientExec   : Target auth state: UNCHALLENGED
-DEBUG o.a.http.impl.execchain.MainClientExec   : Proxy auth state: UNCHALLENGED
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> POST /v0/users HTTP/1.1
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Type: application/json;charset=UTF-8
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Length: 56
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Host: localhost:41827
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Connection: Keep-Alive
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Accept-Encoding: gzip,deflate
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "POST /v0/users HTTP/1.1[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Type: application/json;charset=UTF-8[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Length: 56[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Host: localhost:41827[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "{"id":null,"email":"xenoterracide@gmail.com","new":true}"

或者只是logging.level.org.apache.http.wire=DEBUG似乎包含所有相关信息


4
这是做我想要的最简单的事情。我极力鼓励在接受的答案中加入这一点。
michaelavila '16

22
根据RestTemplate的javadoc :by default the RestTemplate relies on standard JDK facilities to establish HTTP connections. You can switch to use a different HTTP library such as Apache HttpComponents
Ortomala Lokni '17

22
正如@OrtomalaLokni指出的那样,RestTemplate并不默认使用这些Apache类,因此,除了使用它们时如何打印调试信息外,您还应该包括如何使用它们。
曼队长

我越来越像:http-outgoing-0 << "[0x1f][0x8b][0x8][0x0][0x0][0x0][0x0][0x0]
Partha Sarathi Ghosh,

2
@ParthaSarathiGhosh内容可能是gzip编码的,这就是为什么您看不到原始文本的原因。
马修·巴克特

80

用一些代码扩展@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());

直到spring-3.1版本才可用。
Gyan 2014年

3
它不会回答“日志记录响应”的问题,而是留下一个// do日志记录注释。
姜YD

1
进行日志记录很容易,但这仅适用于请求,我没有看到响应主体,假设我有响应对象,但是读取它的流不是一个好主意。
Pavel Niedoba 2015年

11
@PavelNiedoba BufferClientHttpRequestFactory允许读取响应多次。
mjj1409

2
如果您需要在数据库中存储有关请求/响应的信息以进行调试,而常规日志记录不符合您的需求,则此方法效果很好。
GameSalutes

32

最好的选择是添加logging.level.org.springframework.web.client.RestTemplate=DEBUGapplication.properties文件中。

其他解决方案(例如设置)log4j.logger.httpclient.wire将不会始终有效,因为它们假定您使用log4j和Apache HttpClient,但并非总是如此。

但是请注意,该语法仅适用于最新版本的Spring Boot。


5
这不是在记录请求和响应正文,仅记录URL和请求类型(spring-web-4.2.6)
dve 2016年

1
没错,它不是wire日志记录,它仅包含基本信息,例如url,resepone代码,POST参数等
。– gamliela

1
您真正想要的是这个stackoverflow.com/a/39109538/206466
xenoterracide

很好,但是看不到响应正文!
sunleo

辉煌。尽管它不显示响应正文,但它仍然非常有用。谢谢。
克里斯,

30

这些答案都不能真正解决100%的问题。mjj1409获得了大部分信息,但可以方便地避免记录响应的问题,这需要更多的工作。Paul Sabou提供了一个看似现实的解决方案,但没有提供足够的细节来实际实施(这对我完全没有用)。Sofiene获得了日志记录,但存在一个关键问题:由于输入流已被消耗,因此响应不再可读了!

我建议使用BufferingClientHttpResponseWrapper包装响应对象,以允许多次读取响应主体:

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {

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

    @Override
    public ClientHttpResponse intercept(final HttpRequest request, final byte[] body,
            final ClientHttpRequestExecution execution) throws IOException {
        ClientHttpResponse response = execution.execute(request, body);

        response = log(request, body, response);

        return response;
    }

    private ClientHttpResponse log(final HttpRequest request, final byte[] body, final ClientHttpResponse response) {
        final ClientHttpResponse responseCopy = new BufferingClientHttpResponseWrapper(response);
        logger.debug("Method: ", request.getMethod().toString());
        logger.debug("URI: ", , request.getURI().toString());
        logger.debug("Request Body: " + new String(body));
        logger.debug("Response body: " + IOUtils.toString(responseCopy.getBody()));
        return responseCopy;
    }

}

这不会占用InputStream,因为响应主体已加载到内存中并且可以读取多次。如果您的类路径上没有BufferingClientHttpResponseWrapper,则可以在此处找到简单的实现:

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()在出现404时会抛出IOexception,因此您永远不会将响应发送回您的进一步代码,并且通常RestClientResponseException成为ResourceAccessException
MilacH

1
您应该status==200在之前检查responseCopy.getBody()
Anand Rockzz

4
但这是包私有的。您是否将LoggingRequestInterceptor放在包org.springframework.http.client中?
zbstof17年

2
那又如何asyncRestTemplate呢?ListenableFuture当您拦截它时,它需要返回一个a ,这不可能BufferingClientHttpResponseWrapper在回调中更改。
奥马尔法鲁克Almalı

@ÖmerFarukAlmalı在这种情况下,您将需要根据所使用的Guava版本使用链或变换。参见:stackoverflow.com/questions/8191891/…–
詹姆斯·沃特金斯

29

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

最简单的方法是,我只补充说它不能与MockRestServiceServer一起使用,因为它会覆盖requestFactory。
zbstof

工作正常,没有问题,少配置!
sunleo

29

您可以使用spring-rest-template-logger记录RestTemplateHTTP流量。

向您的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

现在,所有RestTemplate HTTP通信都将记录到org.hobsoft.spring.resttemplatelogger.LoggingCustomizer调试级别。

免责声明:我写了这个库。


为什么这个答案被否决?它帮助了我。谢谢@马克·霍布森。
Raffael Bechara Rameh

3
很高兴为@RaffaelBecharaRameh提供了帮助。它最初被否决了,因为我没有嵌入链接项目中的指令。如果您觉得有用,请随时投票!
马克霍布森

您通过Gradle支持吗?
BlackHatSamurai

1
@BlackHatSamurai spring-rest-template-logger是Maven的常规工件,因此它在Gradle上应该可以正常工作。
Mark Hobson

1
嗨@erhanasikoglu,不客气!这是正确的,你可以看到它在这里使用:github.com/markhobson/spring-rest-template-logger/blob/master/...
马克·霍布森

26

我终于找到了一种正确方法来做到这一点。大多数解决方案来自 如何配置Spring和SLF4J,以便可以进行日志记录?

似乎需要完成两件事:

  1. 在log4j.properties中添加以下行: log4j.logger.httpclient.wire=DEBUG
  2. 确保spring不会忽略您的日志记录配置

第二个问题主要发生在使用slf4j的春季环境中(正如我的情况)。因此,使用slf4j时,请确保发生以下两件事:

  1. 您的类路径中没有commons-logging库:这可以通过在pom中添加排除描述符来完成:

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
  2. log4j.properties文件存储在类路径中的某个位置,Spring可以在其中找到/查看它。如果您对此有疑问,那么最后的解决方法是将log4j.properties文件放入默认包中(这不是一种好的做法,而只是为了确保一切按预期运行)


7
这对我不起作用,我做了两件事。我不明白为什么在我的项目中无论如何都没有使用log4j.properties的情况(通过
mvndependency

这对我也不起作用。我什至尝试将根记录器设置为“调试”模式,但还是一无所获。
詹姆斯·沃特金斯

“ httpclient.wire.content”和“ httpclient.wire.header”是Axis2框架中的记录器名称。如果这些请求是使用Axis2完成的,则可用于记录Spring项目中的SOAP请求。
lasspell 2017年

11
httpclient.wire实际上来自Apache HttpComponents HttpClient库(请参阅hc.apache.org/httpcomponents-client-ga/logging.html)。仅当您RestTemplate配置为使用HttpComponentsClientHttpRequestFactory
Scott Frederick

20

记录RestTemplate

选项1.打开调试日志记录。

配置RestTemplate

  • 默认情况下,RestTemplate依靠标准JDK工具建立HTTP连接。您可以切换为使用其他HTTP库,例如Apache HttpComponents

    @Bean public RestTemplate restTemplate(RestTemplateBuilder builder){RestTemplate restTemplate = builder.build(); 返回restTemplate; }

配置日志记录

  • application.yml

    日志记录:级别:org.springframework.web.client.RestTemplate:调试

选项2。使用拦截器

包装回应

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

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

public final class BufferingClientHttpResponseWrapper implements ClientHttpResponse {

    private final ClientHttpResponse response;

    private byte[] body;


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

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

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

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

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

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

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

实施拦截器

package com.example.logging;

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

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

public class LoggingRestTemplate implements ClientHttpRequestInterceptor {

    private final static Logger LOGGER = LoggerFactory.getLogger(LoggingRestTemplate.class);

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

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

    private ClientHttpResponse traceResponse(ClientHttpResponse response) throws IOException {
        if (!LOGGER.isDebugEnabled()) {
            return response;
        }
        final ClientHttpResponse responseWrapper = new BufferingClientHttpResponseWrapper(response);
        StringBuilder inputStringBuilder = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(responseWrapper.getBody(), "UTF-8"));
        String line = bufferedReader.readLine();
        while (line != null) {
            inputStringBuilder.append(line);
            inputStringBuilder.append('\n');
            line = bufferedReader.readLine();
        }
        LOGGER.debug(
                "==========================response begin=============================================");
        LOGGER.debug("Status code    : {}", responseWrapper.getStatusCode());
        LOGGER.debug("Status text    : {}", responseWrapper.getStatusText());
        LOGGER.debug("Headers            : {}", responseWrapper.getHeaders());
        LOGGER.debug("Response body: {}", inputStringBuilder.toString());
        LOGGER.debug(
                "==========================response end===============================================");
        return responseWrapper;
    }

}

配置RestTemplate

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

配置日志记录

  • 检查LoggingRestTemplate的软件包,例如application.yml

    日志记录:级别:com.example.logging:调试

选项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:调试


只是注意:如果要配置TestRestTemplate,请配置RestTemplateBuilder:@Bean public RestTemplateBuilder restTemplateBuilder(){返回新的RestTemplateBuilder()。additionalInterceptors(Collections.singletonList(new LoggingRestTemplate())); }
kingoleg's

还请注意,新的InputStreamReader(responseWrapper.getBody(),StandardCharsets.UTF_8)); 如果“另一端”返回错误,则可能引发错误。您可能需要将其放入try块中。
PeterS

15

---- 2019年7月----

(使用Spring Boot)

令我惊讶的是,Spring Boot尽管具有零配置魔术,却没有提供一种使用RestTemplate检查或记录简单JSON响应正文的简便方法。我浏览了此处提供的各种答案和评论,并分享了我自己的(仍)有效的简化版本,并且在给定当前选项的情况下,对我来说似乎是一个合理的解决方案(我正在将Spring Boot 2.1.6与Gradle 4.4结合使用)

1.使用Fiddler作为http代理

实际上,这是一个非常优雅的解决方案,因为它绕开了创建自己的拦截器或将基础的HTTP客户端更改为apache的所有繁琐工作(请参见下文)。

安装并运行Fiddler

然后

添加-DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888到您的VM选项

2.使用Apache HttpClient

将Apache HttpClient添加到您的Maven或Gradle依赖项。

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

使用HttpComponentsClientHttpRequestFactory作为RequestFactory的RestTemplate。最简单的方法是:

RestTemplate restTemplate = new RestTemplate();

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

application.properties文件中启用DEBUG (如果您使用的是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 Boot Starter依赖项包括spring-boot-starter-logging

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

输出将如下所示:

2019-07-29 11:53:50.265 DEBUG o.s.web.client.RestTemplate : HTTP GET http://www.myrestservice.com/Endpoint?myQueryParam=myValue
2019-07-29 11:53:50.276 DEBUG o.s.web.client.RestTemplate : Accept=[application/json]
2019-07-29 11:53:50.584 DEBUG o.s.web.client.RestTemplate : Response 200 OK
2019-07-29 11:53:50.585 DEBUG o.s.web.client.RestTemplate : Reading to [org.mynamespace.MyJsonModelClass]

2
4号是最简单的调试方法。
Yubaraj

1
2号为我工作。它记录请求的主体。谢谢!
caglar

1
当我谈到这个问题时,我发现3号是一种简便的方法。
Bill Naylor

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年春季版本一起使用,可保持车身完好无损。
乌度(Udo)在

1
在Spring 2.1.10上运行:)谢谢
Moler


8

这可能不是正确的方法,但是我认为这是打印请求和响应而不填太多日志的最简单方法。

通过在以下两行中添加application.properties,可在第一行记录所有请求和响应,以记录请求,在第二行记录响应。

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

记录响应对我不起作用。它只是记录状态代码。是否应该记录有效载荷?
badera

HttpEntityMethodProcessor类(v5.1.8)不记录任何内容。
克里斯,


4

如此之多的响应都需要更改编码和自定义类,这实际上不是必需的。打开调试代理(例如fiddler),然后将Java环境设置为在命令行上使用代理(-Dhttp.proxyHost和-Dhttp.proxyPort),然后运行fiddler,您可以完整地查看请求和响应。它还具有许多辅助优势,例如能够修改结果和响应,然后再将结果和响应发送到进行实验,然后再进行服务器修改。

最后一个可能出现的问题是,如果必须使用HTTPS,则需要从提琴手导出SSL证书,并将其导入到Java密钥库(证书)提示:默认的Java密钥库密码通常为“ changeit”。


1
这使用intellij和小提琴的常规安装对我有用。我编辑了运行配置,并将VM选项设置为-DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888
JD

谢谢!与编写自己的拦截器相比,这是一个非常优雅的解决方案。
克里斯

3

要在Apache HttpClient的帮助下登录到Logback

您需要在类路径中使用Apache HttpClient:

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

配置您RestTemplate使用HttpClient:

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

要记录请求和响应,请添加到Logback配置文件:

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

或记录更多:

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

什么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

如果使用any ,则配置RestTemplatea 的技巧将BufferingClientHttpRequestFactory不起作用ClientHttpRequestInterceptor,如果您尝试通过拦截器进行记录,则将无法使用。这是由于InterceptingHttpAccessorRestTemplate子类)的工作方式。

长话短说...只需使用此类代替RestTemplate(请注意,该类使用SLF4J记录API,根据需要进行编辑):

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;

import javax.annotation.PostConstruct;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.RestTemplate;

/**
 * A {@link RestTemplate} that logs every request and response.
 */
public class LoggingRestTemplate extends RestTemplate {

    // Bleh, this class is not public
    private static final String RESPONSE_WRAPPER_CLASS = "org.springframework.http.client.BufferingClientHttpResponseWrapper";

    private Logger log = LoggerFactory.getLogger(this.getClass());

    private boolean hideAuthorizationHeaders = true;
    private Class<?> wrapperClass;
    private Constructor<?> wrapperConstructor;

    /**
     * Configure the logger to log requests and responses to.
     *
     * @param log log destination, or null to disable
     */
    public void setLogger(Logger log) {
        this.log = log;
    }

    /**
     * Configure the logger to log requests and responses to by name.
     *
     * @param name name of the log destination, or null to disable
     */
    public void setLoggerName(String name) {
        this.setLogger(name != null ? LoggerFactory.getLogger(name) : null);
    }

    /**
     * Configure whether to hide the contents of {@code Authorization} headers.
     *
     * <p>
     * Default true.
     *
     * @param hideAuthorizationHeaders true to hide, otherwise false
     */
    public void setHideAuthorizationHeaders(boolean hideAuthorizationHeaders) {
        this.hideAuthorizationHeaders = hideAuthorizationHeaders;
    }

    /**
     * Log a request.
     */
    protected void traceRequest(HttpRequest request, byte[] body) {
        this.log.debug("xmit: {} {}\n{}{}", request.getMethod(), request.getURI(), this.toString(request.getHeaders()),
          body != null && body.length > 0 ? "\n\n" + new String(body, StandardCharsets.UTF_8) : "");
    }

    /**
     * Log a response.
     */
    protected void traceResponse(ClientHttpResponse response) {
        final ByteArrayOutputStream bodyBuf = new ByteArrayOutputStream();
        HttpStatus statusCode = null;
        try {
            statusCode = response.getStatusCode();
        } catch (IOException e) {
            // ignore
        }
        String statusText = null;
        try {
            statusText = response.getStatusText();
        } catch (IOException e) {
            // ignore
        }
        try (final InputStream input = response.getBody()) {
            byte[] b = new byte[1024];
            int r;
            while ((r = input.read(b)) != -1)
                bodyBuf.write(b, 0, r);
        } catch (IOException e) {
            // ignore
        }
        this.log.debug("recv: {} {}\n{}{}", statusCode, statusText, this.toString(response.getHeaders()),
          bodyBuf.size() > 0 ? "\n\n" + new String(bodyBuf.toByteArray(), StandardCharsets.UTF_8) : "");
    }

    @PostConstruct
    private void addLoggingInterceptor() {
        this.getInterceptors().add(new ClientHttpRequestInterceptor() {
            @Override
            public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution)
              throws IOException {

                // Log request
                if (LoggingRestTemplate.this.log != null && LoggingRestTemplate.this.log.isDebugEnabled())
                    LoggingRestTemplate.this.traceRequest(request, body);

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

                // Log response
                if (LoggingRestTemplate.this.log != null && LoggingRestTemplate.this.log.isDebugEnabled()) {
                    final ClientHttpResponse bufferedResponse = LoggingRestTemplate.this.ensureBuffered(response);
                    if (bufferedResponse != null) {
                        LoggingRestTemplate.this.traceResponse(bufferedResponse);
                        response = bufferedResponse;
                    }
                }

                // Done
                return response;
            }
        });
    }

    private ClientHttpResponse ensureBuffered(ClientHttpResponse response) {
        try {
            if (this.wrapperClass == null)
                this.wrapperClass = Class.forName(RESPONSE_WRAPPER_CLASS, false, ClientHttpResponse.class.getClassLoader());
            if (!this.wrapperClass.isInstance(response)) {
                if (this.wrapperConstructor == null) {
                    this.wrapperConstructor = this.wrapperClass.getDeclaredConstructor(ClientHttpResponse.class);
                    this.wrapperConstructor.setAccessible(true);
                }
                response = (ClientHttpResponse)this.wrapperConstructor.newInstance(response);
            }
            return response;
        } catch (Exception e) {
            this.log.error("error creating {} instance: {}", RESPONSE_WRAPPER_CLASS, e);
            return null;
        }
    }

    private String toString(HttpHeaders headers) {
        final StringBuilder headerBuf = new StringBuilder();
        for (Map.Entry<String, List<String>> entry : headers.entrySet()) {
            if (headerBuf.length() > 0)
                headerBuf.append('\n');
            final String name = entry.getKey();
            for (String value : entry.getValue()) {
                if (this.hideAuthorizationHeaders && name.equalsIgnoreCase(HttpHeaders.AUTHORIZATION))
                    value = "[omitted]";
                headerBuf.append(name).append(": ").append(value);
            }
        }
        return headerBuf.toString();
    }
}

我同意这样做很费力,这是很愚蠢的。


2

除了上面的讨论外,这仅代表快乐场景。如果出现错误,您可能将无法记录响应。

在这种情况下以及上面的所有情况下,您必须重写DefaultResponseErrorHandler并将其设置如下

restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());

2

奇怪的是,这些解决方案都不起作用,因为RestTemplate似乎没有在某些客户端和服务器500x错误上返回响应。在这种情况下,您还可以通过实现ResponseErrorHandler来记录这些内容,如下所示。这是一个草稿代码,但是您明白了:

您可以设置与错误处理程序相同的拦截器:

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

拦截器实现两个接口:

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

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

    public LoggingRequestInterceptor() {
    }

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

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

        return response;
    }

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

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

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

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

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

    }

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

    public void handleError(ClientHttpResponse response) throws IOException {
        this.traceResponse(response);
        defaultResponseErrorHandler.handleError(response);
    }
}

如果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的“最佳答案”看起来是这样,因为使用弹簧RestTemplate时不一定加载httpclient和所有apache.http库。


什么是发行版本?
popalka '17

现在发布的版本是0.2
Moses Meyer

1
易用性很好,但是没有标题
WrRaThY

另外:LoggingRequestInterceptor中所有有用的方法都是私有的,这在扩展方面是一个问题(可以受到保护)
WrRaThY

遗憾的是,我无法在5分钟后编辑评论。登录标头所需要做的就是:log("Headers: {}", request.headers)in LoggingRequestInterceptor:traceRequestlog("Headers: {}", response.headers)in LoggingRequestInterceptor:logResponse。您可能需要考虑添加一些用于记录标题和正文的标志。另外-您可能想要检查正文内容类型以进行日志记录(例如,仅记录application / json *)。这也应该是可配置的。总而言之,通过这些小调整,您将拥有一个不错的库来传播。好工作:)
WrRaThY

0

也想添加我的实现。对于所有缺少的分号,我表示歉意,这是用Groovy编写的。

我需要比提供的答案更可配置的东西。这是一个非常灵活的rest模板bean,它将记录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=================================================")
        }
    }
}

其余模板Bean定义:

@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(post type)发送的消息


跟踪日志记录可能太冗长了...如果每秒有数千个请求怎么办?
Gervasio Amy
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.