我一直在使用Spring RestTemplate一段时间,当我试图调试它的请求和响应时,我一直碰壁。我基本上希望看到当我使用curl并打开“详细”选项时看到的相同内容。例如:
curl -v http://twitter.com/statuses/public_timeline.rss
会同时显示已发送的数据和收到的数据(包括标题,Cookie等)。
我查了一些相关的帖子,比如: How do I log response in Spring RestTemplate? 但我没有设法解决这个问题。
实现此目的的一种方法是实际更改RestTemplate源代码并在那里添加一些额外的日志记录语句,但我发现这种方法确实是最后的手段。应该有一些方法告诉Spring Web Client / RestTemplate以更加友好的方式记录所有内容。
我的目标是能够使用以下代码执行此操作:
restTemplate.put("http://someurl", objectToPut, urlPathValues);
然后在日志文件或控制台中获取相同类型的调试信息(就像我使用curl一样)。 我相信这对使用Spring RestTemplate但有问题的人来说非常有用。使用curl调试RestTemplate问题不起作用(在某些情况下)。
答案 0 :(得分:166)
只需完成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
是必需的,因为我们想在拦截器和初始调用代码中使用响应体。默认实现只允许读取响应主体一次。
答案 1 :(得分:92)
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
似乎包含所有相关信息
答案 2 :(得分:76)
使用一些代码扩展@hstoerr答案:
创建LoggingRequestInterceptor以记录请求响应
public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor {
private static final Logger log = LoggerFactory.getLogger(LoggingRequestInterceptor.class);
@Override
public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
ClientHttpResponse response = execution.execute(request, body);
log(request,body,response);
return response;
}
private void log(HttpRequest request, byte[] body, ClientHttpResponse response) throws IOException {
//do logging
}
}
设置RestTemplate
RestTemplate rt = new RestTemplate();
//set interceptors/requestFactory
ClientHttpRequestInterceptor ri = new LoggingRequestInterceptor();
List<ClientHttpRequestInterceptor> ris = new ArrayList<ClientHttpRequestInterceptor>();
ris.add(ri);
rt.setInterceptors(ris);
rt.setRequestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory());
答案 3 :(得分:27)
这些答案都没有真正解决100%的问题。 mjj1409获得了大部分内容,但是方便地避免了记录响应的问题,这需要更多的工作。保罗萨布提供了一个看似现实的解决方案,但没有提供足够的细节来实际实施(而且它对我来说根本不起作用)。 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,你可以在这里找到简单的实现:
用于设置RestTemplate:
LoggingRequestInterceptor loggingInterceptor = new LoggingRequestInterceptor();
restTemplate.getInterceptors().add(loggingInterceptor);
答案 4 :(得分:24)
我终于找到了以正确的方式做到这一点的方法。 大多数解决方案来自 How do I configure Spring and SLF4J so that I can get logging?
似乎有两件事需要做:
log4j.logger.httpclient.wire=DEBUG
第二个问题主要发生在使用slf4j的弹簧环境中(就像我的情况一样)。 因此,当使用slf4j时,请确保发生以下两件事:
您的类路径中没有公共日志记录库:这可以通过在您的pom中添加排除描述符来完成:
<exclusions><exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
log4j.properties文件存储在类路径中的某个位置,spring可以在其中查找/查看它。如果您遇到此问题,最后的解决方案是将log4j.properties文件放在默认包中(这不是一个好习惯,只是为了看到事情按预期工作)
答案 5 :(得分:23)
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());
答案 6 :(得分:21)
最好的办法是将logging.level.org.springframework.web.client.RestTemplate=DEBUG
添加到application.properties
文件中。
设置log4j.logger.httpclient.wire
等其他解决方案并不总是有效,因为它们假设您使用log4j
和Apache HttpClient
,这并非总是如此。
但请注意,此语法仅适用于最新版本的Spring Boot。
答案 7 :(得分:17)
默认情况下,RestTemplate依赖于标准JDK工具来建立HTTP连接。您可以切换到使用不同的HTTP库,例如Apache HttpComponents
@Bean public RestTemplate restTemplate(RestTemplateBuilder builder){ RestTemplate restTemplate = builder.build(); return restTemplate; }
application.yml
日志记录: 水平: org.springframework.web.client.RestTemplate:DEBUG
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;
}
}
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
RestTemplate restTemplate = builder.build();
restTemplate.setInterceptors(Collections.singletonList(new LoggingRestTemplate()));
return restTemplate;
}
检查LoggingRestTemplate的包,例如在application.yml
中:
日志记录: 水平: com.example.logging:DEBUG
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
RestTemplate restTemplate = builder.build();
restTemplate.setRequestFactory(new HttpComponentsAsyncClientHttpRequestFactory());
return restTemplate;
}
检查LoggingRestTemplate的包,例如在application.yml
中:
日志记录: 水平: org.apache.http:DEBUG
答案 8 :(得分:17)
您可以使用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()
现在所有RestTemplate HTTP流量都将在调试级别记录到org.hobsoft.spring.resttemplatelogger.LoggingCustomizer
。
免责声明:我写了这个图书馆。
答案 9 :(得分:10)
除了描述in the other answer的HttpClient日志记录之外,您还可以引入ClientHttpRequestInterceptor,它读取请求的主体和响应并记录它。如果其他内容也使用HttpClient,或者您需要自定义日志记录格式,则可能需要执行此操作。警告:您需要为RestTemplate提供一个BufferingClientHttpRequestFactory,以便您可以两次读取响应。
答案 10 :(得分:9)
令我惊讶的是,Spring Boot具有“零配置”魔力,却没有提供一种简便的方法来使用RestTemplate检查或记录简单的JSON响应正文。我浏览了此处提供的各种答案和评论,并分享了我自己的(仍)有效的简化版本,并且在当前的情况下,对于我来说,这似乎是一个合理的解决方案(我使用的是Spring Boot 2.1.6和Gradle 4.4。 )
这实际上是一个非常优雅的解决方案,因为它绕开了创建自己的拦截器或将基础的HTTP客户端更改为apache的所有繁琐工作。
安装并运行Fiddler
然后
将
-DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888
添加到您的VM选项
将Apache HttpClient添加到您的Maven或Gradle依赖项中。
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.9</version>
</dependency>
使用HttpComponentsClientHttpRequestFactory
作为RestTemplate的RequestFactory。最简单的方法是:
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
在application.properties
文件中启用调试(如果您使用的是Spring Boot)
logging.level.org.apache.http=DEBUG
如果您使用的是Spring Boot,则需要确保已设置日志记录框架,例如通过使用包含spring-boot-starter-logging
的spring-boot-starter依赖关系。
我将让您通读其他答案和评论中的建议,反建议和陷阱,并自行决定是否要沿这条路走。
尽管这不满足记录主体的规定要求,但这是开始记录REST调用的快速简便的方法。它显示完整的URL和响应状态。
只需将以下行添加到您的application.properties
文件中(假设您使用的是Spring Boot,并假设您使用的是包含spring-boot-starter-logging
的spring boot starter依赖项)
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]
答案 11 :(得分:6)
假设RestTemplate
configured使用HttpClient 4.x,您可以阅读HttpClient的日志记录文档here。记录器与其他答案中指定的记录器不同。
HttpClient 3.x的日志记录配置可用here。
答案 12 :(得分:5)
如其他响应所述,响应主体需要特殊处理,以便可以重复读取(默认情况下,其内容在第一次读取时就被消耗掉)。
在设置请求时,拦截器本身可以包装响应并确保内容被保留并可以重复读取,而不是在设置请求时使用BufferingClientHttpRequestFactory
如响应的消费者):
我的拦截器,
代码:
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", ... }
答案 13 :(得分:5)
这可能不是正确的方法,但我认为这是打印请求和响应的最简单方法,而不会填充太多日志。
通过添加以下2行,application.properties记录所有请求和响应第1行以记录请求,第2行记录响应。
logging.level.org.springframework.web.client.RestTemplate=DEBUG
logging.level.org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor=DEBUG
答案 14 :(得分:4)
使用:
application.properties {
logging.level.org.springframework.web.client=DEBUG
}
或YAML
application.yml {
logging:
level:
root: WARN
org.springframework.web.client: DEBUG
}
答案 15 :(得分:2)
这里有如此多的响应需要更改编码和自定义类,这实际上不是必需的。打开调试代理(例如fiddler),然后将Java环境设置为在命令行上使用代理(-Dhttp.proxyHost和-Dhttp.proxyPort),然后运行fiddler,您可以完整地查看请求和响应。还具有许多辅助优势,例如能够修改结果和响应,然后再发送结果以进行实验,然后再进行服务器修改。
最后一个可能出现的问题是,如果必须使用HTTPS,则需要从提琴手中导出SSL证书,并将其导入到Java密钥库(证书)中。提示:默认的Java密钥库密码通常为“ changeit”
答案 16 :(得分:2)
要在Apache HttpClient的帮助下登录到注销:
您需要在类路径中使用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"/>
答案 17 :(得分: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);
}
}
答案 18 :(得分:2)
除上述讨论外,这仅代表Happy场景。如果出现错误,您可能无法记录响应。
在这种情况下加上上述所有情况,您必须覆盖 DefaultResponseErrorHandler 并将其设置如下
restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());
答案 19 :(得分:2)
如果您使用任何RestTemplate
,那么使用BufferingClientHttpRequestFactory
配置ClientHttpRequestInterceptor
的技巧不起作用,如果您尝试通过拦截器进行登录,则会使用InterceptingHttpAccessor
。这是由于RestTemplate
(RestTemplate
子类)工作的方式。
长话短说...只需使用此类代替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();
}
}
(请注意,它使用SLF4J日志记录API,根据需要进行编辑):
L0
ALOAD 1
LDC 1784196469
INVOKEVIRTUAL dq.c (I)I
ISTORE 2
ILOAD 2
IFNE L1
GOTO L2
L3
RETURN
L1
ALOAD 0
ALOAD 1
ILOAD 2
BIPUSH 15
INVOKEVIRTUAL ac.f (Ldq;IB)V
GOTO L0
L2
GOTO L3
我同意愚蠢的是,这样做需要做很多工作。
答案 20 :(得分:1)
org.apache.http.wire 提供的日志太不可读,所以我使用 logbook 记录应用程序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;
}
}
}
答案 21 :(得分:0)
通过在HttpInputStream上启用多次读取来引用Q / A以记录其余模板的请求和响应
Why my custom ClientHttpRequestInterceptor with empty response
答案 22 :(得分:0)
也想添加我的实现。我为所有丢失的分号道歉,这是用Groovy编写的。
我需要比可接受的答案更可配置的内容。这是一个非常敏捷的休息模板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
}
}
答案 23 :(得分:0)
正如@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=================================================");
}
}
答案 24 :(得分: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的“最佳答案”看起来如此,因为在使用spring RestTemplate时,httpclient和所有apache.http库不一定都会被加载。
答案 25 :(得分:-1)
与使用ClientHttpInterceptor的响应相关,我找到了一种在没有缓冲工厂的情况下保持整个响应的方法。使用一些utils方法将响应体输入流存储在字节数组中,该方法将从body复制该数组,但重要的是,使用try catch包围此方法,因为如果响应为空(这是资源访问异常的原因),它将会中断在catch中只创建空字节数组,而不仅仅是使用该数组和原始响应中的其他参数创建ClientHttpResponse的匿名内部类。您可以将新的ClientHttpResponse对象返回到其余模板执行链,并且可以使用先前存储的正文字节数组来记录响应。这样您就可以避免在实际响应中使用InputStream,并且可以按原样使用Rest Template响应。请注意,如果您的回答太大,这可能会很危险
答案 26 :(得分:-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)发送