Spring RestTemplate – 如何启用完整的debugging/logging请求/响应?

我一直在使用Spring RestTemplate一段时间,当我试图debugging它的请求和响应时,我总是碰壁。 我基本上看到,当我使用curl“verbose”选项打开时看到相同的东西。 例如 :

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

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

我已经检查了一些相关的post,如: 如何在Spring RestTemplate中logging响应? 但我还没有设法解决这个问题。

一种方法是实际更改RestTemplate源代码并在其中添加一些额外的日志logging语句,但是我会发现这种方法真的是最后的手段。 应该有一些方法可以告诉Spring Web Client / RestTemplate以更友好的方式logging所有内容。

我的目标是能够用这样的代码来做到这一点:

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

然后在日志文件或控制台中获得相同types的debugging信息(就像我用curl得到的那样)。 我相信这对于那些使用Spring RestTemplate并且有问题的人来说是非常有用的。 使用curl来debugging你的RestTemplate问题是不行的(在某些情况下)。

只需要完整地实现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.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 { 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.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================================================="); } } 

然后使用BufferingClientHttpRequestFactoryLoggingRequestInterceptor实例化LoggingRequestInterceptor

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

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

用一些代码扩展@hstoerr的答案:


创buildLoggingRequestInterceptor以logging请求响应

 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 Boot中,你可以通过设置属性(或其他12因子方法)来获得完整的请求/响应,

 logging.level.org.apache.http=DEBUG 

这个输出

 -DEBUG .icDefaultHttpClientConnectionOperator : Connecting to localhost/127.0.0.1:41827 -DEBUG .icDefaultHttpClientConnectionOperator : Connection established 127.0.0.1:39546<->127.0.0.1:41827 -DEBUG oahttp.impl.execchain.MainClientExec : Executing request POST /v0/users HTTP/1.1 -DEBUG oahttp.impl.execchain.MainClientExec : Target auth state: UNCHALLENGED -DEBUG oahttp.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 .icDefaultHttpClientConnectionOperator : Connecting to localhost/127.0.0.1:41827 -DEBUG .icDefaultHttpClientConnectionOperator : Connection established 127.0.0.1:39546<->127.0.0.1:41827 -DEBUG oahttp.impl.execchain.MainClientExec : Executing request POST /v0/users HTTP/1.1 -DEBUG oahttp.impl.execchain.MainClientExec : Target auth state: UNCHALLENGED -DEBUG oahttp.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这似乎包含所有的相关信息

我终于find了一个正确的方法来做到这一点。 大部分解决scheme来自我如何configurationSpring和SLF4J,以便能够logging日志?

看来有两件事情需要做:

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

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

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

      <exclusions><exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> 
  2. log4j.properties文件存储在springpath中的某处,spring可以查找/查看它。 如果你有这个问题,最后的解决办法是将log4j.properties文件放在默认的包中(这不是一个好的做法,只是为了看到事情按照你的预期工作)

这些答案都没有解决100%的问题。 mjj1409得到了大部分,但方便地避免了logging响应的问题,这需要更多的工作。 Paul Sabou提供了一个看起来很现实的解决scheme,但是没有提供足够的细节来实际执行(而且对我来说根本不起作用)。 Sofiene得到了日志logging,但有一个严重的问题:响应不再可读,因为inputstream已经被使用了!

我build议使用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,因为响应主体被加载到内存中,可以被多次读取。 如果你的类path没有BufferingClientHttpResponseWrapper,你可以在这里find简单的实现:

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

除了在另一个答案中描述的HttpClient日志之外,还可以引入一个ClientHttpRequestInterceptor,它读取请求的主体和响应,并将其logging下来。 如果其他东西也使用HttpClient,或者如果你想要一个自定义的日志logging格式,你可能想要这样做。 警告:您需要为RestTemplate提供一个BufferingClientHttpRequestFactory,以便您可以读取两次响应。

由异噻菌胺给予的解决scheme使用

 logging.level.org.apache.http=DEBUG 

是好的,但问题是,默认情况下不使用Apache HttpComponents。

要使用Apache HttpComponents添加到您的pom.xml

 <dependency> <groupId>org.apache.httpcomponents</groupId> <artifactId>httpasyncclient</artifactId> </dependency> 

并configurationRestTemplate

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

最好的办法是将logging.level.org.springframework.web.client.RestTemplate=DEBUG添加到application.properties文件中。

像设置log4j.logger.httpclient.wire其他解决scheme将不会总是工作,因为他们假设您使用log4j和Apache HttpClient ,这并不总是如此。

但是请注意,这个语法只适用于最新版本的Spring Boot。

假设RestTemplate configuration为使用HttpClient 4.x,那么可以在这里阅读HttpClient的日志logging文档。 logging器与其他答案中指定的logging器不同。

HttpClient 3.x的日志configuration可以在这里find 。

除上述讨论之外,这只代表快乐情景。 如果发生错误,您可能无法logging响应。

在这种情况下,再加上上面的所有情况,你必须重写DefaultResponseErrorHandler并像下面那样设置它

 restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl()); 

使用BufferingClientHttpRequestFactoryconfigurationRestTemplate的技巧在您使用任何ClientHttpRequestInterceptor ,如果您尝试通过拦截器进行login,则会出现这种情况。 这是由于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(); } } 

我同意这是愚蠢的,它需要这么多的工作才能做到这一点。

现在最好的解决scheme,只需添加依赖项:

 <dependency> <groupId>com.github.zg2pro</groupId> <artifactId>spring-rest-basis</artifactId> <version>vx</version> </dependency> 

它包含一个LoggingRequestInterceptor类,您可以将其添加到RestTemplate:

通过将其作为拦截器添加到弹簧RestTemplate中来集成此实用工具,方法如下:

 restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build()); 

并添加一个slf4j实现到你的框架,如log4j。

直接使用“Zg2proRestTemplate” 。 @PaulSabou的“最佳答案”看起来如此,因为在使用Spring RestTemplate时,httpclient和所有的apache.http库不一定会被加载。

奇怪的是,这些解决scheme都没有工作,因为RestTemplate似乎没有返回一些客户端和服务器500x错误的响应。 在这种情况下,你将通过如下实现ResponseErrorHandler来logging这些事件。 这是一个草案的代码,但你明白了一点:

您可以将相同的拦截器设置为error handling程序:

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

我的logging器configuration使用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,你需要继续debugging,在我的情况下,我得到这个:

 genericMessageConverter.write(requestBody, requestBodyType, requestContentType, httpRequest); 

和这个:

 outputMessage.getBody().flush(); 

outputMessage.getBody()包含消息http(发布types)发送

您可以使用spring-rest-template-loggerloggingRestTemplate HTTPstream量。

添加一个依赖到你的Maven项目:

 <dependency> <groupId>org.hobsoft.spring</groupId> <artifactId>rest-template-logger</artifactId> <version>0.1.0</version> </dependency> 

然后按如下方式自定义RestTemplate

 RestTemplate restTemplate = new RestTemplateBuilder() .customizers(new LoggingCustomizer()) .build() 

现在,所有RestTemplate HTTPstream量都将在debugging级别logging到org.hobsoft.spring.resttemplatelogger.LoggingCustomizer

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