我已经使用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问题是行不通的(在某些情况下)。
下面是我用来在RestTemplate中记录整个http请求/响应而不丢失响应体信息的解决方案。Spring引导版本为<version>2.7.5</version>
1.创建LoggingInterceptor类
@Component
@Slf4j
public class LoggingInterceptor implements ClientHttpRequestInterceptor {
@Override
public ClientHttpResponse intercept(final HttpRequest request, final byte[] body, final ClientHttpRequestExecution execution) throws IOException {
traceRequest(request, body);
ClientHttpResponse response = execution.execute(request, body);
response = traceResponse(response);
return response;
}
private void traceRequest(HttpRequest request, byte[] body) throws IOException {
if (!log.isDebugEnabled()) {
return;
}
log.debug("=========================== Request Begin ===========================");
log.debug("URI : " + request.getURI());
log.debug("Method : " + request.getMethod());
log.debug("Headers : " + request.getHeaders());
log.debug("Body : " + new String(body, "utf-8"));
log.debug("============================ Request End ============================");
}
private ClientHttpResponse traceResponse(ClientHttpResponse response) throws IOException {
if (!log.isDebugEnabled()) {
return response;
}
ClientHttpResponse newCopiedResponse = new BufferingClientHttpResponseWrapper(response);
StringBuilder inputStringBuilder = new StringBuilder();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(newCopiedResponse.getBody(), "UTF-8"));
String line = bufferedReader.readLine();
while (line != null) {
inputStringBuilder.append(line);
line = bufferedReader.readLine();
}
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 ============================");
return newCopiedResponse;
}
/**
* Wrapper around ClientHttpResponse, buffers the body so it can be read repeatedly (for logging & consuming the result).
*/
private static class BufferingClientHttpResponseWrapper implements ClientHttpResponse {
private final ClientHttpResponse response;
private byte[] body;
public BufferingClientHttpResponseWrapper(ClientHttpResponse response) {
this.response = response;
}
@Override
public InputStream getBody() throws IOException {
if (body == null) {
body = StreamUtils.copyToByteArray(response.getBody());
}
return new ByteArrayInputStream(body);
}
@Override
public HttpStatus getStatusCode() throws IOException {
return this.response.getStatusCode();
}
@Override
public int getRawStatusCode() throws IOException {
return this.response.getRawStatusCode();
}
@Override
public String getStatusText() throws IOException {
return this.response.getStatusText();
}
@Override
public HttpHeaders getHeaders() {
return this.response.getHeaders();
}
@Override
public void close() {
this.response.close();
}
}
}
2.将其附加到RestTemplate bean
@Configuration
public class RestTemplateConfig {
@Bean
public RestTemplate createRestTemplate(LoggingInterceptor loggingInterceptor) {
RestTemplate restTemplate = new RestTemplate();
restTemplate.setInterceptors(Collections.singletonList(loggingInterceptor));
return restTemplate;
}
}
3.在应用程序中应用适当的级别
logging:
level:
com:
test: DEBUG
wire提供了太多不可读的日志,所以我使用日志记录应用程序Servlet和RestTemplate请求和响应的有效负载。
build.gradle:
compile group: 'org.zalando', name: 'logbook-spring-boot-starter', version: '2.6.2'
或Maven依赖:
<dependency>
<groupId>org.zalando</groupId>
<artifactId>logbook-spring-boot-starter</artifactId>
<version>2.6.2</version>
</dependency>
应用程序。属性(或槽YAML):
logging.level.org.zalando.logbook = TRACE
RestTemplate.java:
import java.util.function.Supplier;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import org.zalando.logbook.httpclient.LogbookHttpRequestInterceptor;
import org.zalando.logbook.httpclient.LogbookHttpResponseInterceptor;
@Configuration
public class RestTemplateConfiguration {
private final LogbookHttpRequestInterceptor logbookHttpRequestInterceptor;
private final LogbookHttpResponseInterceptor logbookHttpResponseInterceptor;
public RestTemplateConfiguration(LogbookHttpRequestInterceptor logbookHttpRequestInterceptor,
LogbookHttpResponseInterceptor logbookHttpResponseInterceptor) {
this.logbookHttpRequestInterceptor = logbookHttpRequestInterceptor;
this.logbookHttpResponseInterceptor = logbookHttpResponseInterceptor;
}
@Bean
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return 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();
return new HttpComponentsClientHttpRequestFactory(client);
}
}
}
我终于找到了正确的方法。
大部分的解决方案来自
我如何配置Spring和SLF4J以获得日志记录?
看来有两件事需要做:
在log4j中添加以下行。属性:log4j.logger.httpclient.wire=DEBUG
确保spring不会忽略您的日志配置
第二个问题主要发生在使用slf4j的spring环境中(就像我的例子一样)。
因此,当使用slf4j时,请确保发生以下两件事:
There is no commons-logging library in your classpath : this can be done by adding the exclusion descriptors in your pom :
<exclusions><exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
The log4j.properties file is stored somewhere in the classpath where spring can find/see it. If you have problems with this, a last resort solution would be to put the log4j.properties file in the default package (not a good practice but just to see that things work as you expect)