web-dev-qa-db-ja.com

Spring RestTemplate - リクエスト/レスポンスのフルデバッグ/ロギングを有効にする方法

私はしばらくの間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を使ってもうまくいかない(場合によっては)。

172
Paul Sabou

私はついにこれを正しい方法で行う方法を見つけました。ほとんどの解決策は ロギングを取得できるようにSpringとSLF4Jを構成するにはどうすればよいですか?

実行する必要があることが2つあるようです。

  1. Log4j.propertiesに次の行を追加します。log4j.logger.httpclient.wire=DEBUG
  2. Springがあなたのロギング設定を無視しないことを確認してください

2番目の問題は、slf4jが使用されている(それが私の場合のように)春の環境で主に起こります。そのため、slf4jを使用するときは、次の2つのことが必ず行われるようにしてください。

  1. あなたのクラスパスにcommons-loggingライブラリはありません:これはあなたのpomに除外記述子を追加することによって行うことができます:

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
    
  2. Log4j.propertiesファイルは、springが見つけられる/見ることができるクラスパス内のどこかに格納されています。これに問題がある場合、最後の解決策はlog4j.propertiesファイルをデフォルトのパッケージに入れることです(良い習慣ではありませんが、期待通りに動作することを確認するだけです)。

24
Paul Sabou

リクエストとレスポンスを追跡するための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=================================================");
    }

}

それからRestTemplateBufferingClientHttpRequestFactoryを使ってLoggingRequestInterceptorをインスタンス化します。

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

BufferingClientHttpRequestFactoryは、インターセプターと最初の呼び出しコードの両方にレスポンスボディを使用したいため、必須です。デフォルトの実装ではレスポンスボディを一度だけ読むことができます。

169

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だけで、関連する情報がすべて含まれているようです。

96
xenoterracide

いくつかのコードで@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());
76
mjj1409

これらの答えのどれも実際に問題の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がない場合は、単純な実装をここで見つけることができます。

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);
27
James Watkins

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());
23
Ortomala Lokni

最善の策は、logging.level.org.springframework.web.client.RestTemplate=DEBUGapplication.propertiesファイルに追加することです。

log4j.logger.httpclient.wireの設定のような他の解決策は、あなたがlog4jとApacheのHttpClientを使うと仮定するので、いつもうまくいくとは限りません。

ただし、この構文は最新バージョンのSpring Bootでのみ機能します。

22
gamliela

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に記録されます。

免責事項:私はこのライブラリを書きました。

18
Mark Hobson

ロギングRestTemplate

オプション1。デバッグログを開きます。

RestTemplateの設定

  • デフォルトでは、 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

オプション2.インターセプターを使用する

ラッパーレスポンス

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

}

RestTemplateの設定

@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

オプション3. httpcomponentを使用する

Httpcomponentの依存関係をインポートする

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

RestTemplateの設定

@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

17
user2746033

説明されているHttpClientロギングの他に その他の回答で 、あなたはリクエストの本体とレスポンスを読んでそれをログに記録するClientHttpRequestInterceptorを導入することもできます。他のものもHttpClientを使用している場合、またはカスタムロギングフォーマットが必要な場合は、これを実行することをお勧めします。注意:応答を2回読み取ることができるように、RestTemplateにBufferingClientHttpRequestFactoryを指定することをお勧めします。

10

他のレスポンスで述べたように、レスポンスボディは特別な扱いを必要としますので、繰り返し読むことができます(デフォルトでは、その内容は最初の読み込みで消費されます)。

リクエストを設定するときにBufferingClientHttpRequestFactoryを使用する代わりに、インターセプター自体がレスポンスをラップし、コンテンツが保持され、繰り返し読み取ることができることを確認できます(ロガーだけでなく、応答の消費者によって):

私の迎撃機、

  • ラッパーを使ってレスポンスボディをバッファリングする
  • よりコンパクトな方法でログを記録する
  • ステータスコード識別子も記録します(例:201 Created)
  • リクエストシーケンス番号を含み、同時ログエントリを複数のスレッドと簡単に区別できるようにします。

コード:

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", ...  }
7
Peter Walser

HttpClient 4.xを使用するためにRestTemplateconfigured であると仮定すると、HttpClientのロギングドキュメントを読むことができます ここ 。ロガーは他の回答で指定されたものとは異なります。

HttpClient 3.xのロギング設定が利用可能です ここ

6

これはそれを行う正しい方法ではないかもしれませんが、私はこれがあまりにも多くのログを埋めることなく要求と応答を印刷するための最も簡単なアプローチであると思います。

2行以下を追加することで、application.propertiesはすべての要求と応答を1行目に記録し、2行目に応答を記録します。

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

5
User9123

---- 2019年7月----

(Spring Bootを使用)

ゼロ構成の魔法をすべて備えたSpring Bootが、RestTemplateを使用して単純なJSON応答本文を簡単に検査またはログに記録する方法を提供していないことに驚きました。ここで提供されているさまざまな回答とコメントを見て、現在のオプションを考えると、(まだ)動作するものの独自の蒸留バージョンを共有しており、合理的なソリューションのように思えます(Gradle 4.4でSpring Boot 2.1.6を使用しています)

1. FiddlerをHTTPプロキシとして使用する

これは実際には非常にエレガントなソリューションです。独自のインターセプターを作成したり、基礎となるhttpクライアントをApacheに変更したりする面倒な作業をすべてバイパスするためです(以下を参照)。

インストールして実行します Fiddler

その後

VMオプションに-DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888を追加します

2. Apache HttpClientの使用

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依存関係を使用する。

3.インターセプターを使用する

他の回答やコメントにある提案、反対提案、落とし穴を読んで、その道を進んで行きたいかどうかを自分で決めましょう。

4.ボディなしのログURLと応答ステータス

これは、本文を記録するという規定の要件を満たしていませんが、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]
4
Chris

つかいます:

application.properties{ logging.level.org.springframework.web.client=DEBUG }

またはYAML

application.yml{ logging: level:
root: WARN org.springframework.web.client: DEBUG }

4
Elton Sandré

そのため、ここでの回答の多くはコーディングの変更とカスタマイズされたクラスを必要とし、実際には必要ありません。 fiddlerなどのデバッグプロキシを作成し、コマンドライン(-Dhttp.proxyHostおよび-Dhttp.proxyPort)でプロキシを使用するようにJava環境を設定してから、fiddlerを実行すると、要求と応答の全体を確認できます。また、サーバーの変更を確定する前に実験を実行するために送信される前後の結果や応答を調整することができるなど、多くの付随的な利点もあります。

HTTPSを使用する必要がある場合、問題の最後の部分は、SSL証明書をfiddlerからエクスポートし、それをJavaキーストア(cacerts)にインポートする必要があるというヒントです。デフォルトのJavaキーストアのパスワードは通常「changeit」です。

2
Lee Burch

上記の議論に加えて、これは幸せなシナリオを表すだけです。 エラーが来た場合、おそらくあなたは応答を記録することができないでしょう。

この場合、上記のすべての場合に加えて、DefaultResponseErrorHandlerをオーバーライドして、以下のように設定する必要があります。

restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());
2
user666

奇妙なことに、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);
    }
}
2
kisna

RestTemplateBufferingClientHttpRequestFactoryで設定するコツは、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();
    }
}

これを行うにはこれだけの作業が必要なのは愚かだと思います。

2
Archie

HttpInputStreamの複数の読み取りを有効にして、残りのテンプレートの要求と応答をログに記録するためのQ/Aを参照してください。

なぜ空の応答を持つ私のカスタムClientHttpRequestInterceptor

0
maya16

@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=================================================");
    }

}
0
Tony Findeisen

私もこれを実装したかったのです。不足しているすべてのセミコロンをお詫び申し上げます。これは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
    }
}
0
Jason Slobotski

最善の解決策は、依存関係を追加するだけです。

<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を使用するときに必ずしもロードされているわけではないので、そう見えます。

0
Moses Meyer