Spring RestTemplate-リクエスト/レスポンスの完全なデバッグ/ロギングを有効にする方法は?


220

私はしばらくの間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を使用していて問題がある人にとって非常に役立つと思います。curlを使用してRestTemplateの問題をデバッグしても、(一部のケースでは)機能しません。


30
2018年に読んだ人への警告:これに対する簡単な答えはありません!
davidfrancis

3
最も簡単な方法は、AbstractHttpMessageConverterクラスのwrite(...)メソッドでブレークポイントを使用することです。データを表示できるoutputMessageオブジェクトがあります。PS値をコピーして、オンラインフォーマッタでフォーマットできます。
Sergey Chepurnov 2018年

1
これは春に簡単にできるように思えますが、ここでの答えから判断すると-ケースではありません。したがって、もう1つの解決策は、Springを完全にバイパスし、Fiddlerなどのツールを使用して要求/応答をキャプチャすることです。
michaelok


2019年7月:この質問に対する簡単な解決策はまだないので、私は他の24の回答(これまでのところ)の要約とそれらのコメントとディスカッションを以下の自分の回答で説明しようとしました。それが役に立てば幸い。
Chris

回答:


206

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、a BufferingClientHttpRequestFactoryとthe を使用してインスタンス化しますLoggingRequestInterceptor

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

BufferingClientHttpRequestFactory私たちが迎撃に、最初の呼び出し元のコードの両方のレスポンスボディを使用するよう要求されています。デフォルトの実装では、応答本文を1回だけ読み取ることができます。


27
これは間違っています。ストリームを読み取る場合、アプリケーションコードは応答を読み取ることができません。
James Watkins

28
RestTemplateにBufferingClientHttpRequestFactoryを指定したので、応答を2回読み取ることができます。
sofiene zaghdoudi 2016年

16
私たちはこのテクニックを約3か月間使用しています。BufferingClientHttpResponseWrapper@sofienezaghdoudiが示唆するとおりに構成されたRestTemplateでのみ機能します。ただし、MockRestServiceServer.createServer(restTemplate)RequestFactoryをに上書きするため、SpringのmockServerフレームワークを使用するテストで使用すると機能しませんInterceptingClientHttpRequestFactory
RubesMN 2016年

8
テクニックは良いです、実装は間違っています。404の場合、response.getBody()はIOExceptionをスローします->ログを取得できず、最悪の場合でも、RestClientResponseExceptionではなく、以降のコードでResourceAccessExceptionになります
MilacH

5
返信いただきありがとうございます。しかし、これは他の多くのログに広がる可能性があるため、複数の「log.debug」を持つことは悪い習慣です。すべてが同じ場所にあることが確実になるように、単一のlog.debug命令を使用することをお勧めします
user2447161

127

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":"xenoterracide@gmail.com","new":true}"

と応答

-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connecting to localhost/127.0.0.1:41827
-DEBUG .i.c.DefaultHttpClientConnectionOperator : Connection established 127.0.0.1:39546<->127.0.0.1:41827
-DEBUG o.a.http.impl.execchain.MainClientExec   : Executing request POST /v0/users HTTP/1.1
-DEBUG o.a.http.impl.execchain.MainClientExec   : Target auth state: UNCHALLENGED
-DEBUG o.a.http.impl.execchain.MainClientExec   : Proxy auth state: UNCHALLENGED
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> POST /v0/users HTTP/1.1
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Type: application/json;charset=UTF-8
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Content-Length: 56
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Host: localhost:41827
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Connection: Keep-Alive
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)
-DEBUG org.apache.http.headers                  : http-outgoing-0 >> Accept-Encoding: gzip,deflate
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "POST /v0/users HTTP/1.1[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Type: application/json;charset=UTF-8[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Content-Length: 56[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Host: localhost:41827[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Connection: Keep-Alive[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "User-Agent: Apache-HttpClient/4.5.2 (Java/1.8.0_102)[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "Accept-Encoding: gzip,deflate[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "[\r][\n]"
-DEBUG org.apache.http.wire                     : http-outgoing-0 >> "{"id":null,"email":"xenoterracide@gmail.com","new":true}"

またはlogging.level.org.apache.http.wire=DEBUG、関連するすべての情報が含まれているように見える


4
これは、私がやりたいことをする最も簡単なことでした。これを受け入れられた回答に含めることを強くお勧めします。
michaelavila

22
RestTemplateのjavadocによると:by default the RestTemplate relies on standard JDK facilities to establish HTTP connections. You can switch to use a different HTTP library such as Apache HttpComponents
Ortomala Lokni 2017

22
@OrtomalaLokniで指摘されているように、RestTemplateはこれらのApacheクラスをデフォルトでは使用しないため、使用されているときにデバッグを印刷する方法に加えて、それらの使用方法も含める必要があります。
キャプテンマン

私はこのようになっています:http-outgoing-0 << "[0x1f][0x8b][0x8][0x0][0x0][0x0][0x0][0x0]
パルタサラティゴーシュ2018

2
@ParthaSarathiGhoshコンテンツはおそらくgzipエンコードされているため、生のテキストが表示されません。
Matthew Buckett、

80

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

これは、spring-3.1バージョンまで使用できません。
ギャン2014年

3
「ロギング応答」の質問には答えませんが、代わりに// doロギングコメントを残します。
Jiang YD、2015年

1
ロギングを行うのは簡単でしたが、これはリクエストに対してのみ機能します。レスポンス本文は表示されません。レスポンスオブジェクトがあると仮定しますが、そのストリームを読み取ることはお勧めできません。
Pavel Niedoba 2015年

11
@PavelNiedoba BufferClientHttpRequestFactoryを使用すると、応答を複数回読み取ることができます。
mjj1409 2015

2
これは、デバッグのためにリクエスト/レスポンスに関する情報をデータベースに保存する必要があり、通常のロギングがニーズに合わない場合にうまく機能します。
GameSalutes 2016年

32

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

設定などの他のソリューションlog4j.logger.httpclient.wirelog4j、Apache を使用することを前提としているため、常に機能するとは限りませんHttpClientは限りませんが、常にそうであるとは限りません。

ただし、この構文はSpring Bootの最新バージョンでのみ機能することに注意してください。


5
これは、リクエストとレスポンスの本文をログに記録するのではなく、URLとリクエストタイプ(spring-web-4.2.6)のみを記録します
dve

1
あなたはそれがありませんが、正しいですwire、それはURLのみ、reseponeコード、POSTパラメータなどのような重要な情報が含まれ、伐採
gamliela

1
あなたが本当に欲しいのはこのstackoverflow.com/a/39109538/206466
xenoterracide

結構ですがレスポンスボディが見えませんでした!
サンレオ2017

鮮やかさ。応答本文は出力されませんが、それでも非常に便利です。ありがとうございました。
Chris

30

これらの答えのどれも実際に問題の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);

同じように、404の場合、responseCopy.getBody()はIOexceptionをスローします。そのため、応答をさらにコードに送り返すことはなく、通常はRestClientResponseExceptionがResourceAccessException
MilacH

1
status==200以前に確認する必要がありますresponseCopy.getBody()
Anand Rockzz 2017年

4
しかし、それはパッケージプライベートです。LoggingRequestInterceptorをパッケージ「org.springframework.http.client」に入れましたか?
zbstof 2017年

2
どうasyncRestTemplateですか?コールバックでListenableFuture変更できないインターセプト時にaを返す必要がありBufferingClientHttpResponseWrapperます。
オメルファルクAlmalı

@ÖmerFarukAlmalıその場合、使用しているGuavaのバージョンに応じて、チェーンまたは変換を使用する必要があります。参照:stackoverflow.com/questions/8191891/…–
James Watkins、

29

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

最も簡単な方法は、requestFactoryを上書きするため、MockRestServiceServerでは機能しないことだけを追加します。
zbstof 2017年

うまく機能し、設定の問題はありません!
サンレオ2017

29

あなたは使用することができ、ばね、残りのテンプレートロガーをログに記録しますRestTemplate HTTPトラフィックを。

Mavenプロジェクトに依存関係を追加します。

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

次にRestTemplate、次のようにカスタマイズします。

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

でデバッグログが有効になっていることを確認しapplication.propertiesます。

logging.level.org.hobsoft.spring.resttemplatelogger.LoggingCustomizer = DEBUG

これで、すべてのRestTemplate HTTPトラフィックがログに記録されます org.hobsoft.spring.resttemplatelogger.LoggingCustomizerデバッグレベルでれます。

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


なぜこの回答は反対投票されるのですか?それは私を助けました。ありがとう、@ Mark Hobson。
Raffael Bechara Rameh、2018

3
@RaffaelBecharaRamehに役立ちました。リンクされたプロジェクトからの指示を埋め込まなかったため、最初は反対票が投じられました。役に立ったと思ったら、遠慮なく投票してください!
マークホブソン

Gradleでサポートしますか?
BlackHatSamurai

1
@BlackHatSamurai spring-rest-template-loggerは通常のMavenアーティファクトなので、Gradleで正常に動作するはずです。
マークホブソン

1
こんにちは@erhanasikogluさん、どういたしまして!:それはあなたがここで使用されてそれを見ることができ、そうですgithub.com/markhobson/spring-rest-template-logger/blob/master/...
マーク・ホブソン

26

私は最終的にこれを正しい方法で行う方法を見つけました。ほとんどのソリューションは、ロギングを取得できるようにSpringとSLF4Jを構成するには どうすればよいですか?

次の2つのことを行う必要があるようです。

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

2番目の問題は、slf4jが使用されているSpring環境で主に発生します(私の場合と同様)。そのため、slf4jを使用する場合は、次の2つのことを確認してください。

  1. クラスパスにcommons-loggingライブラリはありません。これは、pomに除外記述子を追加することで実行できます。

            <exclusions><exclusion>
                <groupId>commons-logging</groupId>
                <artifactId>commons-logging</artifactId>
            </exclusion>
        </exclusions>
  2. log4j.propertiesファイルは、クラスパスのどこかに格納されており、Springはそれを検出/表示できます。これに問題がある場合、最後の解決策は、log4j.propertiesファイルをデフォルトのパッケージに入れることです(良い方法ではなく、期待どおりに機能することを確認するだけです)。


7
これは私にはうまくいきません、私は両方のことをしました。プロジェクトで使用されていないのになぜlog4j.propertiesを配置する必要があるのか​​理解できません(mvn dependency:treeで確認)
Pavel Niedoba

これも私にはうまくいきません。ルートロガーをデバッグモードに設定しても、何もしませんでした。
James Watkins

「httpclient.wire.content」と「httpclient.wire.header」は、Axis2フレームワークのロガー名です。これらは、Axis2を使用して行われる場合、SpringリクエストなどのSOAPリクエストをログに記録するために使用できます。
lasspell 2017年

11
httpclient.wire実際には、Apache HttpComponents HttpClientライブラリからのものです(hc.apache.org/httpcomponents-client-ga/logging.htmlを参照)。このテクニックはRestTemplateHttpComponentsClientHttpRequestFactory
Scott Frederick

20

RestTemplateのロギング

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

RestTemplateの構成

  • デフォルトでは、RestTemplateは標準JDK機能に依存してHTTP接続を確立します。Apache HttpComponentsなどの別のHTTPライブラリを使用するように切り替えることができます。

    @Bean public RestTemplate restTemplate(RestTemplateBuilder builder){RestTemplate restTemplate = builder.build(); restTemplateを返します。}

構成されたロギング

  • application.yml

    ロギング:レベル: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;
}

構成されたロギング

  • LoggingRestTemplateのパッケージを確認してください。例application.yml

    ロギング:レベル: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;
}

構成されたロギング

  • LoggingRestTemplateのパッケージを確認してください。例application.yml

    ロギング:レベル:org.apache.http:DEBUG


注:構成する場合はTestRestTemplate、構成しますRestTemplateBuilder。@Bean public RestTemplateBuilder restTemplateBuilder(){return new RestTemplateBuilder()。additionalInterceptors(Collections.singletonList(new LoggingRestTemplate())); }
kingoleg 2018

また、新しいInputStreamReader(responseWrapper.getBody()、StandardCharsets.UTF_8));にも注意してください。「もう一方の端」がエラーを返した場合、エラーをスローできます。あなたはそれをtryブロックに入れたいかもしれません。
PeterS

15

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

(Spring Bootを使用)

Spring Bootは、Zero Configurationのすべての魔法を備えているため、RestTemplateを使用して単純なJSON応答本文を検査または記録する簡単な方法を提供していないことに驚きました。私はここで提供されるさまざまな回答とコメントを調べ、現在のオプションを考えると、(まだ)機能するものの独自の蒸留バージョンを共有しており、合理的な解決策のように思えます(Gradle 4.4でSpring Boot 2.1.6を使用しています) )

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

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

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

その後

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

2. Apache HttpClientの使用

Apache HttpClientをMavenまたはGradleの依存関係に追加します。

<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.9</version>
</dependency>

HttpComponentsClientHttpRequestFactoryRestTemplateのRequestFactoryとして使用します。これを行う最も簡単な方法は次のとおりです。

RestTemplate restTemplate = new RestTemplate();

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

application.propertiesファイルでデバッグを有効にする(Spring Bootを使用している場合)

logging.level.org.apache.http=DEBUG

Spring Bootを使用している場合は、ロギングフレームワークが設定されていることを確認する必要があります。たとえば、を含むspring-boot-starter依存関係を使用しますspring-boot-starter-logging

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

他の回答やコメントで提案、反対提案、および落とし穴を読み、その道を進むかどうかを自分で決定します。

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

これは本文をログに記録するという要件を満たしていませんが、REST呼び出しのログを記録するための迅速で簡単な方法です。完全なURLと応答ステータスが表示されます。

application.propertiesファイルに次の行を追加するだけです(Spring Bootを使用していることを前提とし、Spring Boot Starterの依存関係を使用していると想定していますspring-boot-starter-logging)。

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]

2
4番が最も簡単なデバッグ方法です。
Yubaraj

1
2位は私のために働いた。リクエストの本文を記録します。ありがとうございました!
caglar

1
この問題に遭遇したとき、私は3番がこれを行う簡単な方法だと思いました。
Bill Naylor

12

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


12

他の応答で述べたように、応答本文は繰り返し読み取ることができるように特別な処理が必要です(デフォルトでは、最初の読み取りで内容が消費されます)。

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

1
これは、2019年春のバージョンと連動して、体を傷つけません。
ウドは

1
春2.1.10で動作します:)ありがとう
Moler

8

application.properties

logging.level.org.springframework.web.client=DEBUG

application.yml

logging:
  level:  
    root: WARN
    org.springframework.web.client: DEBUG

8

これは正しい方法ではないかもしれませんが、これは、ログに多くの情報を入力せずに要求と応答を印刷するための最も簡単な方法だと思います。

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

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

応答のロギングは機能しません。ステータスコードをログに記録するだけです。ペイロードを記録する必要がありますか?
badera

クラスHttpEntityMethodProcessor(v5.1.8)は何もログに記録しません。
Chris


4

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

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


1
これは、intellijとフィドルの定期的なインストールを使用して私に働きました。実行構成を編集し、VMオプションをに設定しました-DproxySet=true -Dhttp.proxyHost=localhost -Dhttp.proxyPort=8888
JD

ありがとう!これは、独自のInterceptorを作成するのに比べてかなりエレガントなソリューションです。
Chris

3

Logbackへのロギング用Apache HttpClientの助けを借り:

クラスパスにApache HttpClientが必要です。

<dependency>
  <groupId>org.apache.httpcomponents</groupId>
  <artifactId>httpclient</artifactId>
  <version>4.5.10</version>
</dependency>

RestTemplateHttpClientを使用するように設定します。

restTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory());

リクエストとレスポンスをログに記録するには、Logback構成ファイルに追加します。

<logger name="org.apache.http.wire" level="DEBUG"/>

またはさらにログする:

<logger name="org.apache.http" level="DEBUG"/>

どのようなログバック構成ファイル?
G_V

1
テストの場合は@G_V logback.xmlまたはlogback-test.xml。
holmis83

それはまたで動作しorg.apache.http.wire=DEBUG、あなたにapplication.properties
G_V

Spring-Bootを使用している場合は@G_V。私の答えはブートなしで動作します。
holmis83

2

を使用している場合、を使用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();
    }
}

これを行うためだけにこれだけの労力がかかるのはばかげていると私は同意します。


2

上記の議論に加えて、これはハッピーシナリオのみを表しています。エラーの場合、おそらく応答をログに記録することはできません。が発生した。

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

restTemplate.setErrorHandler(new DefaultResponseErrorHandlerImpl());

2

不思議なことに、RestTemplateが一部のクライアントとサーバーの500xエラーで応答を返さないように見えるため、これらのソリューションはどれも機能しません。その場合、ResponseErrorHandlerを次のように実装することで、これらもログに記録します。これがドラフトコードですが、要点はわかります。

エラーハンドラーと同じインターセプターを設定できます。

restTemplate.getInterceptors().add(interceptor);
restTemplate.setRequestFactory(new BufferingClientHttpRequestFactory(new SimpleClientHttpRequestFactory()));
restTemplate.setErrorHandler(interceptor);

そして傍受は両方のインターフェースを実装します:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashSet;
import java.util.Set;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus.Series;
import org.springframework.http.client.ClientHttpRequestExecution;
import org.springframework.http.client.ClientHttpRequestInterceptor;
import org.springframework.http.client.ClientHttpResponse;
import org.springframework.web.client.DefaultResponseErrorHandler;
import org.springframework.web.client.ResponseErrorHandler;

public class LoggingRequestInterceptor implements ClientHttpRequestInterceptor, ResponseErrorHandler {
    static final Logger log = LoggerFactory.getLogger(LoggingRequestInterceptor.class);
    static final DefaultResponseErrorHandler defaultResponseErrorHandler = new DefaultResponseErrorHandler();
    final Set<Series> loggableStatuses = new HashSet();

    public LoggingRequestInterceptor() {
    }

    public LoggingRequestInterceptor(Set<Series> loggableStatuses) {
        loggableStatuses.addAll(loggableStatuses);
    }

    public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
        this.traceRequest(request, body);
        ClientHttpResponse response = execution.execute(request, body);
        if(response != null) {
            this.traceResponse(response);
        }

        return response;
    }

    private void traceRequest(HttpRequest request, byte[] body) throws IOException {
        log.debug("===========================request begin================================================");
        log.debug("URI         : {}", request.getURI());
        log.debug("Method      : {}", request.getMethod());
        log.debug("Headers     : {}", request.getHeaders());
        log.debug("Request body: {}", new String(body, "UTF-8"));
        log.debug("==========================request end================================================");
    }

    private void traceResponse(ClientHttpResponse response) throws IOException {
        if(this.loggableStatuses.isEmpty() || this.loggableStatuses.contains(response.getStatusCode().series())) {
            StringBuilder inputStringBuilder = new StringBuilder();

            try {
                BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(response.getBody(), "UTF-8"));

                for(String line = bufferedReader.readLine(); line != null; line = bufferedReader.readLine()) {
                    inputStringBuilder.append(line);
                    inputStringBuilder.append('\n');
                }
            } catch (Throwable var5) {
                log.error("cannot read response due to error", var5);
            }

            log.debug("============================response begin==========================================");
            log.debug("Status code  : {}", response.getStatusCode());
            log.debug("Status text  : {}", response.getStatusText());
            log.debug("Headers      : {}", response.getHeaders());
            log.debug("Response body: {}", inputStringBuilder.toString());
            log.debug("=======================response end=================================================");
        }

    }

    public boolean hasError(ClientHttpResponse response) throws IOException {
        return defaultResponseErrorHandler.hasError(response);
    }

    public void handleError(ClientHttpResponse response) throws IOException {
        this.traceResponse(response);
        defaultResponseErrorHandler.handleError(response);
    }
}

bodyがmultipart / form-dataである場合、ログからバイナリデータ(ファイルコンテンツ)を簡単に除外できますか?
ルーク、

1

@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

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

<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による「ベストアンサー」はそのように見えます。SpringRestTemplateを使用する場合、httpclientとすべてのapache.httpライブラリは必ずしもロードされないためです。


リリースされたバージョンは何ですか?
popalka 2017

現在リリースされているバージョンは0.2です
Moses Meyer

1
使いやすさは素晴らしいですが、ヘッダーがありません
WrRaThY

さらに、LoggingRequestInterceptorのすべての便利なメソッドはプライベートです。これは、拡張機能に関しては問題になります(保護される可能性があります)
WrRaThY

残念ながら、5分後にはコメントを編集できません。ヘッダーをログに記録するために必要なのは、log("Headers: {}", request.headers)in LoggingRequestInterceptor:traceRequestlog("Headers: {}", response.headers)inだけLoggingRequestInterceptor:logResponseです。ヘッダーと本文をログに記録するためのいくつかのフラグを追加することを検討してください。また、ログの本文コンテンツタイプを確認することもできます(たとえば、application / json *のみをログに記録します)。これも構成可能でなければなりません。全体として、これらの小さな調整により、広めるのに最適なライブラリができます。よくできました:)
WrRaThY 2017

0

これの私の実装も追加したかった。欠落しているセミコロンをすべてお詫び申し上げます。これはGroovyで書かれています。

提供された承認済みの回答よりも構成可能なものが必要でした。以下は、非常に機敏で、OPが探しているすべてのものをログに記録するRESTテンプレート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=================================================")
        }
    }
}

残りのテンプレート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

org.apache.http.wireがログを読み取れないため、ログブックを使用してアプリケーションサーブレットをログに記録し、RestTemplate req / respをログに記録します。

build.gradle

compile group: 'org.zalando', name: 'logbook-spring-boot-starter', version: '1.13.0'

application.properties

logging.level.org.zalando.logbook:TRACE

RestTemplate

@Configuration
public class RestTemplateConfig {

@Autowired
private LogbookHttpRequestInterceptor logbookHttpRequestInterceptor;

@Autowired
private LogbookHttpResponseInterceptor logbookHttpResponseInterceptor;

@Bean
public RestTemplate restTemplate() {
    return new RestTemplateBuilder()
        .requestFactory(new MyRequestFactorySupplier())
        .build();
}

class MyRequestFactorySupplier implements Supplier<ClientHttpRequestFactory> {

    @Override
    public ClientHttpRequestFactory get() {
        // Using Apache HTTP client.
        CloseableHttpClient client = HttpClientBuilder.create()
            .addInterceptorFirst(logbookHttpRequestInterceptor)
            .addInterceptorFirst(logbookHttpResponseInterceptor)
            .build();
        HttpComponentsClientHttpRequestFactory clientHttpRequestFactory = new HttpComponentsClientHttpRequestFactory(client);
        return clientHttpRequestFactory;
    }

}
}

-1

ClientHttpInterceptorを使用した応答に関連して、バッファリングファクトリなしで応答全体を保持する方法を見つけました。本文からその配列をコピーするいくつかのutilsメソッドを使用して、応答本文の入力ストリームをバイト配列内に格納するだけですが、重要なのは、このメソッドをtry catchで囲みます。キャッチでは、空のバイト配列を作成し、その配列と元の応答からの他のパラメーターを使用して、ClientHttpResponseの匿名の内部クラスを作成するだけです。その新しいClientHttpResponseオブジェクトを残りのテンプレート実行チェーンに返すことができ、以前に格納された本文のバイト配列を使用して応答をログに記録できます。そうすれば、実際の応答でInputStreamを消費することを避け、Rest Template応答をそのまま使用できます。注意、


-2

私のロガー構成はxmlを使用しました

<logger name="org.springframework.web.client.RestTemplate">
    <level value="trace"/>
</logger>

それからあなたは以下のようなものを得るでしょう:

DEBUG org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:92) : Reading [com.test.java.MyClass] as "application/json" using [org.springframework.http.converter.json.MappingJackson2HttpMessageConverter@604525f1]

HttpMessageConverterExtractor.java:92を介して、デバッグを続行する必要があり、私の場合、これを取得しました:

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

この:

outputMessage.getBody().flush();

outputMessage.getBody()には、http(投稿タイプ)が送信するメッセージが含まれます


トレースロギングは冗長すぎるかもしれません... 1秒あたり数千のリクエストがある場合はどうなりますか?
Gervasio Amy
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.