私はしばらくの間Spring RestTemplateを使用してきましたが、要求と応答をデバッグしようとすると常に壁に突き当たります。私は基本的に私が "verbose"オプションをオンにしてcurlを使用したときに見られるのと同じことを見たいと思っています。例えば :
curl -v http://Twitter.com/statuses/public_timeline.rss
送信データと受信データ(ヘッダー、Cookieなどを含む)の両方を表示します。
以下のような関連記事をチェックしました。 Spring RestTemplateで応答を記録する方法は? しかし、この問題を解決できていません。
これを行う1つの方法は、実際にRestTemplateのソースコードを変更し、そこにいくつかのロギングステートメントを追加することですが、このアプローチは本当に最後の手段になると思います。 Spring Web Client/RestTemplateに、もっと親切な方法ですべてを記録するように指示する方法があるはずです。
私の目標は、これを次のようなコードで実行できるようにすることです。
restTemplate.put("http://someurl", objectToPut, urlPathValues);
次に、ログファイルまたはコンソールに同じ種類のデバッグ情報(curlで取得したものと同じ)を取得します。私はこれがSpring RestTemplateを使っていて問題を抱えている人にとっては非常に役に立つと思います。あなたのRestTemplate問題をデバッグするためにcurlを使ってもうまくいかない(場合によっては)。
私はついにこれを正しい方法で行う方法を見つけました。ほとんどの解決策は ロギングを取得できるようにSpringとSLF4Jを構成するにはどうすればよいですか?
実行する必要があることが2つあるようです。
log4j.logger.httpclient.wire=DEBUG
2番目の問題は、slf4jが使用されている(それが私の場合のように)春の環境で主に起こります。そのため、slf4jを使用するときは、次の2つのことが必ず行われるようにしてください。
あなたのクラスパスにcommons-loggingライブラリはありません:これはあなたのpomに除外記述子を追加することによって行うことができます:
<exclusions><exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
Log4j.propertiesファイルは、springが見つけられる/見ることができるクラスパス内のどこかに格納されています。これに問題がある場合、最後の解決策はlog4j.propertiesファイルをデフォルトのパッケージに入れることです(良い習慣ではありませんが、期待通りに動作することを確認するだけです)。
リクエストとレスポンスを追跡するための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
は、インターセプターと最初の呼び出しコードの両方にレスポンスボディを使用したいため、必須です。デフォルトの実装ではレスポンスボディを一度だけ読むことができます。
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":"[email protected]","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":"[email protected]","new":true}"
またはlogging.level.org.Apache.http.wire=DEBUG
だけで、関連する情報がすべて含まれているようです。
いくつかのコードで@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());
これらの答えのどれも実際に問題の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がない場合は、単純な実装をここで見つけることができます。
RestTemplateを設定するには
LoggingRequestInterceptor loggingInterceptor = new LoggingRequestInterceptor();
restTemplate.getInterceptors().add(loggingInterceptor);
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());
最善の策は、logging.level.org.springframework.web.client.RestTemplate=DEBUG
をapplication.properties
ファイルに追加することです。
log4j.logger.httpclient.wire
の設定のような他の解決策は、あなたがlog4j
とApacheのHttpClient
を使うと仮定するので、いつもうまくいくとは限りません。
ただし、この構文は最新バージョンのSpring Bootでのみ機能します。
RestTemplate
というHTTPトラフィックを記録するには、 spring-rest-template-logger を使用できます。
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
に記録されます。
免責事項:私はこのライブラリを書きました。
デフォルトでは、 RestTemplate はHTTP接続を確立するために標準のJDK機能に依存します。 Apache HttpComponentsなどの別のHTTPライブラリを使用するように切り替えることができます。
@Bean public RestTemplate restTemplate(RestTemplateBuilderビルダー){RestTemplate restTemplate = builder.build(); restTemplateを返します。 }
application.yml
logging:level: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;
}
例えばapplication.yml
でLoggingRestTemplateのパッケージをチェックしてください。
logging:level: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;
}
例えばapplication.yml
でLoggingRestTemplateのパッケージをチェックしてください。
logging:level:org.Apache.http:DEBUG
説明されているHttpClientロギングの他に その他の回答で 、あなたはリクエストの本体とレスポンスを読んでそれをログに記録するClientHttpRequestInterceptorを導入することもできます。他のものもHttpClientを使用している場合、またはカスタムロギングフォーマットが必要な場合は、これを実行することをお勧めします。注意:応答を2回読み取ることができるように、RestTemplateにBufferingClientHttpRequestFactoryを指定することをお勧めします。
他のレスポンスで述べたように、レスポンスボディは特別な扱いを必要としますので、繰り返し読むことができます(デフォルトでは、その内容は最初の読み込みで消費されます)。
リクエストを設定するときに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", ... }
HttpClient 4.xを使用するためにRestTemplate
が configured であると仮定すると、HttpClientのロギングドキュメントを読むことができます ここ 。ロガーは他の回答で指定されたものとは異なります。
HttpClient 3.xのロギング設定が利用可能です ここ 。
これはそれを行う正しい方法ではないかもしれませんが、私はこれがあまりにも多くのログを埋めることなく要求と応答を印刷するための最も簡単なアプローチであると思います。
2行以下を追加することで、application.propertiesはすべての要求と応答を1行目に記録し、2行目に応答を記録します。
logging.level.org.springframework.web.client.RestTemplate=DEBUG
logging.level.org.springframework.web.servlet.mvc.method.annotation.HttpEntityMethodProcessor=DEBUG
ゼロ構成の魔法をすべて備えたSpring Bootが、RestTemplateを使用して単純なJSON応答本文を簡単に検査またはログに記録する方法を提供していないことに驚きました。ここで提供されているさまざまな回答とコメントを見て、現在のオプションを考えると、(まだ)動作するものの独自の蒸留バージョンを共有しており、合理的なソリューションのように思えます(Gradle 4.4でSpring Boot 2.1.6を使用しています)
これは実際には非常にエレガントなソリューションです。独自のインターセプターを作成したり、基礎となるhttpクライアントをApacheに変更したりする面倒な作業をすべてバイパスするためです(以下を参照)。
インストールして実行します Fiddler
その後
VMオプションに
-DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888
を追加します
Apache HttpClientをMavenまたはGradleの依存関係に追加します。
<dependency>
<groupId>org.Apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.9</version>
</dependency>
RestTemplateのRequestFactoryとしてHttpComponentsClientHttpRequestFactory
を使用します。これを行う最も簡単な方法は次のとおりです。
RestTemplate restTemplate = new RestTemplate();
restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());
application.properties
ファイルでDEBUGを有効にします(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]
つかいます:
application.properties{ logging.level.org.springframework.web.client=DEBUG }
またはYAML
application.yml{ logging: level:
root: WARN org.springframework.web.client: DEBUG }
そのため、ここでの回答の多くはコーディングの変更とカスタマイズされたクラスを必要とし、実際には必要ありません。 fiddlerなどのデバッグプロキシを作成し、コマンドライン(-Dhttp.proxyHostおよび-Dhttp.proxyPort)でプロキシを使用するようにJava環境を設定してから、fiddlerを実行すると、要求と応答の全体を確認できます。また、サーバーの変更を確定する前に実験を実行するために送信される前後の結果や応答を調整することができるなど、多くの付随的な利点もあります。
HTTPSを使用する必要がある場合、問題の最後の部分は、SSL証明書をfiddlerからエクスポートし、それをJavaキーストア(cacerts)にインポートする必要があるというヒントです。デフォルトのJavaキーストアのパスワードは通常「changeit」です。
上記の議論に加えて、これは幸せなシナリオを表すだけです。 エラーが来た場合、おそらくあなたは応答を記録することができないでしょう。
この場合、上記のすべての場合に加えて、DefaultResponseErrorHandlerをオーバーライドして、以下のように設定する必要があります。
restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());
奇妙なことに、RestTemplateはいくつかのクライアントとサーバーの500倍のエラーで応答を返さないように見えるので、これらの解決策のどれもうまくいきません。その場合は、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);
}
}
RestTemplate
をBufferingClientHttpRequestFactory
で設定するコツは、ClientHttpRequestInterceptor
を使用している場合には機能しません。インターセプタを介してログに記録しようとしている場合にはうまくいきます。これはInterceptingHttpAccessor
(どのRestTemplate
サブクラス)が機能するかによるものです。
手短に言うと、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();
}
}
これを行うにはこれだけの作業が必要なのは愚かだと思います。
HttpInputStreamの複数の読み取りを有効にして、残りのテンプレートの要求と応答をログに記録するためのQ/Aを参照してください。
@MilacHが指摘したように、実装にエラーがあります。 statusCode> 400が返されると、インターセプタからerrorHandlerが呼び出されないため、IOExceptionがスローされます。例外は無視することができ、その後ハンドラメソッドで再びキャッチされます。
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=================================================");
}
}
私もこれを実装したかったのです。不足しているすべてのセミコロンをお詫び申し上げます。これはGroovyで書かれています。
私は提供された受け入れられた答えよりもっと構成可能な何かを必要としました。これは非常に敏捷で、OPが探しているようにすべてをログに記録する残りのテンプレートBeanです。
カスタムロギングインターセプタクラス
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
}
}
最善の解決策は、依存関係を追加するだけです。
<dependency>
<groupId>com.github.zg2pro</groupId>
<artifactId>spring-rest-basis</artifactId>
<version>v.x</version>
</dependency>
それはあなたがあなたのRestTemplateにその方法を追加することができるLoggingRequestInterceptorクラスを含みます:
次のようにして、このユーティリティをSpring RestTemplateのインターセプターとして追加して統合します。
restTemplate.setRequestFactory(LoggingRequestFactoryFactory.build());
そしてlog4jのようにあなたのフレームワークにslf4jの実装を追加してください。
または直接 "Zg2proRestTemplate"を使用。 @PaulSabouによる「ベストアンサー」は、httpclientおよびすべてのApache.http libsが春のRestTemplateを使用するときに必ずしもロードされているわけではないので、そう見えます。