我已经使用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问题是行不通的(在某些情况下)。


当前回答

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

通过添加以下2行应用。Properties记录所有请求和响应,第一行记录请求,第二行记录响应。

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

其他回答

So many responses here require coding changes and customized classes and it really is not necessary. Gte a debugging proxy such as fiddler and set your java environment to use the proxy on the command line (-Dhttp.proxyHost and -Dhttp.proxyPort) then run fiddler and you can see the requests and responses in their entirety. Also comes with many ancillary advantages such as the ability to tinker with the results and responses before and after they are sent to run experiments before committing to modification of the server.

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

用一些代码扩展@hstoerr answer:


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

除了在另一个答案中描述的HttpClient日志之外,您还可以引入一个ClientHttpRequestInterceptor,它读取请求体和响应并记录它。如果其他东西也使用HttpClient,或者你想要一个自定义的日志格式,你可能需要这样做。注意:您需要给RestTemplate一个BufferingClientHttpRequestFactory,这样您就可以读取两次响应。

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

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

我还想加上这个的实现。我为所有缺少的分号感到抱歉,这是用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=================================================")
        }
    }
}

Rest模板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
    }
}