Spring WebClient呼び出しのログ記録

1. 概要

このチュートリアルでは、https://www.baeldung.com/spring-5-webclient [Spring's _WebClient_](リアクティブHTTPクライアント)をカスタマイズして、要求と応答をログに記録する方法を示します。

2. WebClient

_WebClient_は、https://www.baeldung.com/spring-webflux [Spring WebFlux]に基づいたHTTPリクエスト用のリアクティブでノンブロッキングのインターフェースです。 機能的で流なAPIを備え、宣言型の構成のための反応型を備えています。
背後で、_WebClient_はHTTPクライアントを呼び出します。 Reactor Nettyがデフォルトであり、Jettyのリアクティブ_HttpClient_もサポートされています。 さらに、_WebClient_に_ClientConnector_を設定することにより、HTTPクライアントの他の実装をプラグインすることができます。

3. リクエストとレスポンスのロギング

_WebClient_で使用されるデフォルトの_HttpClient_はNetty実装です。したがって、_reactor.netty.http.client_ロギングレベルを_DEBUGに変更した後、*いくつかのリクエストロギングを確認できますが、カスタマイズしたログが必要な場合は、configur__e__ link:/spring-webclient-filters[_WebClient#filters_]を介したロガー:
WebClient
  .builder()
  .filters(exchangeFilterFunctions -> {
      exchangeFilterFunctions.add(logRequest());
      exchangeFilterFunctions.add(logResponse());
  })
  .build()
このコードスニペットでは、要求と応答をログに記録する2つの個別のフィルターを追加しました。
_ExchangeFilterFunction#ofRequestProcessor_を使用して_logRequest_を実装しましょう。
ExchangeFilterFunction logRequest() {
    return ExchangeFilterFunction.ofRequestProcessor(clientRequest -> {
        if (log.isDebugEnabled()) {
            StringBuilder sb = new StringBuilder("Request: \n");
            //append clientRequest method and url
            clientRequest
              .headers()
              .forEach((name, values) -> values.forEach(value -> /* append header key/value */));
            log.debug(sb.toString());
        }
        return Mono.just(clientRequest);
    });
}
_logResponse_も同じですが、代わりに_ExchangeFilterFunction#ofResponseProcessor_を使用する必要があります。 + *
これで、_reactor.netty.http.client_ログレベルを_INFO_または_ERROR_に変更して、よりクリーンな出力を得ることができます。

4. ボディを使用したリクエストとレスポンスのロギング

HTTPクライアントには、リクエストとレスポンスの本文を記録する機能があります。 したがって、*目標を達成するには、_WebClient ._ *でログ対応のHTTPクライアントを使用します。
これを行うには、JettyおよびNetty HTTPクライアントで__WebClient.Builder#clientConnector – __let's seeを手動で設定します。

4.1. Jetty _HttpClient_を使用したロギング

まず、https://search.maven.org/search?q = a:jetty-reactive-httpclient [_jetty-reactive-httpclient_]のMaven依存関係をpomに追加しましょう。
<dependency>
    <groupId>org.eclipse.jetty</groupId>
    <artifactId>jetty-reactive-httpclient</artifactId>
    <version>1.0.3</version>
</dependency>
次に、カスタマイズされたJetty _HttpClient_を作成します。
SslContextFactory.Client sslContextFactory = new SslContextFactory.Client();
HttpClient httpClient = new HttpClient(sslContextFactory) {
    @Override
    public Request newRequest(URI uri) {
        Request request = super.newRequest(uri);
        return enhance(request);
    }
};
ここでは、_HttpClient#newRequest_をオーバーライドしてから、_Request_をログエンハンサーでラップしました。
次に、リクエストの各部分が利用可能になったときにログを記録できるように、リクエストにイベントを登録する必要があります。
Request enhance(Request request) {
    StringBuilder group = new StringBuilder();
    request.onRequestBegin(theRequest -> {
        // append request url and method to group
    });
    request.onRequestHeaders(theRequest -> {
        for (HttpField header : theRequest.getHeaders()) {
            // append request headers to group
        }
    });
    request.onRequestContent((theRequest, content) -> {
        // append content to group
    });
    request.onRequestSuccess(theRequest -> {
        log.debug(group.toString());
        group.delete(0, group.length());
    });
    group.append("\n");
    request.onResponseBegin(theResponse -> {
        // append response status to group
    });
    request.onResponseHeaders(theResponse -> {
        for (HttpField header : theResponse.getHeaders()) {
            // append response headers to group
        }
    });
    request.onResponseContent((theResponse, content) -> {
        // append content to group
    });
    request.onResponseSuccess(theResponse -> {
        log.debug(group.toString());
    });
    return request;
}
最後に、_WebClient_インスタンスを構築する必要があります。
WebClient
  .builder()
  .clientConnector(new JettyClientHttpConnector(httpClient))
  .build()
もちろん、前と同じように、_RequestLogEnhancer_のログレベルを_DEBUG_に設定する必要があります。

4.2. Netty _HttpClient_を使用したロギング


最初に、Netty _HttpClient_を作成しましょう。
HttpClient httpClient = HttpClient
  .create()
  .wiretap(true)
盗聴を有効にすると、各リクエストとレスポンスが詳細に記録されます。
次に、Nettyのクライアントパッケージ_reactor.netty.http.client_のログレベルを_DEBUG_に設定する必要があります。
logging.level.reactor.netty.http.client=DEBUG
それでは、_WebClient_をビルドしましょう。
WebClient
  .builder()
  .clientConnector(new ReactorClientHttpConnector(httpClient))
  .build()
_WebClient_はすべてのリクエストとレスポンスを詳細に記録しますが、Nettyビルトインロガーのデフォルト形式には、16進数とテキストの両方のボディ表現*と、リクエストとレスポンスイベントに関する多くのデータが含まれます。
したがって、Neti用のカスタムロガーが必要な場合は、Httpクライアントを構成できます。
HttpClient httpClient = HttpClient
  .create()
  .tcpConfiguration(
    tc -> tc.bootstrap(
      b -> BootstrapHandlers.updateLogSupport(b, new CustomLogger(HttpClient.class))))
  .build()
最後に、_LoggingHandler_を拡張する_CustomLogger_を実装しましょう。
public class CustomLogger extends LoggingHandler {
    public CustomLogger(Class<?> clazz) {
        super(clazz);
    }

    @Override
    protected String format(ChannelHandlerContext ctx, String event, Object arg) {
        if (arg instanceof ByteBuf) {
            ByteBuf msg = (ByteBuf) arg;
            return decode(
              msg, msg.readerIndex(), msg.readableBytes(), defaultCharset());
        }
        return super.format(ctx, event, arg);
    }

    // further code omitted for brevity
}

5. 結論

このチュートリアルでは、Spring _WebClient_を使用しながら、要求データと応答データを記録するためのいくつかの手法を使用しました。
いつものように、コードはhttps://github.com/eugenp/tutorials/tree/master/spring-5-reactive-client[GitHub上]で入手できます。