春 - 受信リクエストをログに記録する

春–受信リクエストのログ

1. 前書き

このクイックチュートリアルでは、Springのロギングフィルターを使用して着信リクエストをロギングする基本を示します。 ロギングを始めたばかりの場合は、このlogging intro articleSLF4J articleを確認してください。

2. Mavenの依存関係

ロギングの依存関係は、紹介記事の依存関係と同じになります。ここにSpringを追加してみましょう。


    org.springframework
    spring-core
    5.0.5.RELEASE

spring-coreの最新バージョンはここにあります。

3. 基本的なWebコントローラー

まず、この例で使用するコントローラーを定義しましょう。

@RestController
public class TaxiFareController {

    @GetMapping("/taxifare/get/")
    public RateCard getTaxiFare() {
        return new RateCard();
    }

    @PostMapping("/taxifare/calculate/")
    public String calculateTaxiFare(
      @RequestBody @Valid TaxiRide taxiRide) {

        // return the calculated fare
    }
}

4. カスタムリクエストログ

Springは、Web要求の前後にアクションを実行するようにユーザー定義インターセプターを構成するメカニズムを提供します。

Springリクエストインターセプターの中で注目すべきインターフェースの1つはHandlerInterceptorであり、これを使用して次のメソッドを実装することで着信リクエストをログに記録できます。

  1. preHandle() –このメソッドは、実際のコントローラーサービスメソッドの前に実行されます

  2. afterCompletion() –このメソッドは、コントローラーが応答を送信する準備ができた後に実行されます

さらに、Springは、ユーザーが拡張できるHandlerInterceptorAdaptorクラスの形式でHandlerInterceptorインターフェースのデフォルトの実装を提供します。

HandlerInterceptorAdaptor asを拡張して、独自のインターセプターを作成しましょう。

@Component
public class TaxiFareRequestInterceptor
  extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle(
      HttpServletRequest request,
      HttpServletResponse response,
      Object handler) {
        return true;
    }

    @Override
    public void afterCompletion(
      HttpServletRequest request,
      HttpServletResponse response,
      Object handler,
      Exception ex) {
        //
    }
}

最後に、MVCライフサイクル内のTaxiRideRequestInterceptorを構成して、TaxiFareControllerクラスで定義されたパス/taxifareにマップされるコントローラーメソッド呼び出しの前処理と後処理をキャプチャします。

@Configuration
public class TaxiFareMVCConfig implements WebMvcConfigurer {

    @Autowired
    private TaxiFareRequestInterceptor taxiFareRequestInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(taxiFareRequestInterceptor)
          .addPathPatterns("/**/taxifare/**/");
    }
}

結論として、WebMvcConfigurerは、addInterceptors()メソッドを呼び出すことにより、Spring MVCライフサイクル内にTaxiFareRequestInterceptorを追加します。

最大の課題は、ロギングのためにリクエストとレスポンスのペイロードのコピーを取得し、リクエストされたペイロードをサーブレットが処理できるようにすることです。

読み取り要求の主な問題は、入力ストリームが初めて読み取られるとすぐに、消費済みとしてマークされ、再度読み取ることができないことです。

アプリケーションは、要求ストリームの読み取り後に例外をスローします。

{
  "timestamp": 1500645243383,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.http.converter
    .HttpMessageNotReadableException",
  "message": "Could not read document: Stream closed;
    nested exception is java.io.IOException: Stream closed",
  "path": "/rest-log/taxifare/calculate/"
}

To overcome this problemの場合、キャッシュを利用してリクエストストリームを保存し、ログに使用できます。

Springには、ロギングの目的でリクエストデータをキャッシュするために使用できるContentCachingRequestWrapperContentCachingResponseWrapperなどの便利なクラスがいくつか用意されています。

ContentCachingRequestWrapperクラスを使用してリクエストオブジェクトをキャッシュするように、TaxiRideRequestInterceptorクラスのpreHandle()を調整しましょう。

@Override
public boolean preHandle(HttpServletRequest request,
  HttpServletResponse response, Object handler) {

    HttpServletRequest requestCacheWrapperObject
      = new ContentCachingRequestWrapper(request);
    requestCacheWrapperObject.getParameterMap();
    // Read inputStream from requestCacheWrapperObject and log it
    return true;
}

ご覧のとおり、実際のリクエストオブジェクトに影響を与えることなく、ロギング用のペイロードデータを読み取るために使用できるContentCachingRequestWrapperクラスを使用してリクエストオブジェクトをキャッシュしました。

requestCacheWrapperObject.getContentAsByteArray();

制限

  • ContentCachingRequestWrapperクラスは、以下のみをサポートします。

Content-Type:application/x-www-form-urlencoded
Method-Type:POST
  • 次のメソッドを呼び出して、要求データが使用前にContentCachingRequestWrapperにキャッシュされていることを確認する必要があります。

requestCacheWrapperObject.getParameterMap();

5. Springビルトインリクエストロギング

Springは、ペイロードを記録する組み込みソリューションを提供します。 構成を使用してSpringアプリケーションにプラグインすることにより、既製のフィルターを使用できます。

AbstractRequestLoggingFilterは、ロギングの基本機能を提供するフィルターです。 サブクラスは、beforeRequest()メソッドとafterRequest()メソッドをオーバーライドして、リクエストの実際のロギングを実行する必要があります。

Springフレームワークは、着信要求を記録するために使用できる3つの具体的な実装クラスを提供します。 これらの3つのクラスは次のとおりです。

  • CommonsRequestLoggingFilter

  • Log4jNestedDiagnosticContextFilter(非推奨)

  • ServletContextRequestLoggingFilter

それでは、CommonsRequestLoggingFilterに移動して、ロギングの着信要求をキャプチャするように構成しましょう。

5.1. SpringBootアプリケーションを構成する

Spring Bootアプリケーションは、Beanの定義を追加して要求のログを有効にすることで構成できます。

@Configuration
public class RequestLoggingFilterConfig {

    @Bean
    public CommonsRequestLoggingFilter logFilter() {
        CommonsRequestLoggingFilter filter
          = new CommonsRequestLoggingFilter();
        filter.setIncludeQueryString(true);
        filter.setIncludePayload(true);
        filter.setMaxPayloadLength(10000);
        filter.setIncludeHeaders(false);
        filter.setAfterMessagePrefix("REQUEST DATA : ");
        return filter;
    }
}

また、このログフィルタでは、ログレベルをDEBUGに設定する必要があります。 以下の要素をlogback.xmlに追加することで、DEBUGモードを有効にできます。


    

DEBUGレベルのログを有効にする別の方法は、application.propertiesに以下を追加することです。

logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=
  DEBUG

5.2. 従来のWebアプリケーションを構成する

標準のSpringWebアプリケーションでは、FilterはXML構成またはJava構成のいずれかを介して設定できます。 従来のJavaベースの構成を使用してCommonsRequestLoggingFilterを設定しましょう。

ご存知のとおり、CommonsRequestLoggingFilterincludePayload属性はデフォルトでfalseに設定されています。 Java構成を使用してコンテナーに挿入する前に、includePayloadを有効にするために、属性の値をオーバーライドするカスタムクラスが必要になります。

public class CustomeRequestLoggingFilter
  extends CommonsRequestLoggingFilter {

    public CustomeRequestLoggingFilter() {
        super.setIncludeQueryString(true);
        super.setIncludePayload(true);
        super.setMaxPayloadLength(10000);
    }
}

ここで、Java based web initializerを使用してCustomeRequestLoggingFilterを注入する必要があります。

public class CustomWebAppInitializer implements
  WebApplicationInitializer {
    public void onStartup(ServletContext container) {

        AnnotationConfigWebApplicationContext context
          = new AnnotationConfigWebApplicationContext();
    context.setConfigLocation("com.example");
    container.addListener(new ContextLoaderListener(context));

    ServletRegistration.Dynamic dispatcher
          = container.addServlet("dispatcher",
          new DispatcherServlet(context));
    dispatcher.setLoadOnStartup(1);
    dispatcher.addMapping("/");

    container.addFilter("customRequestLoggingFilter",
          CustomeRequestLoggingFilter.class)
          .addMappingForServletNames(null, false, "dispatcher");
    }
}

6. 実例

これで、コンテキストでSpring Bootを接続し、着信要求のログが期待どおりに機能することを実際に確認できます。

@Test
public void givenRequest_whenFetchTaxiFareRateCard_thanOK() {
    TestRestTemplate testRestTemplate = new TestRestTemplate();
    TaxiRide taxiRide = new TaxiRide(true, 10l);
    String fare = testRestTemplate.postForObject(
      URL + "calculate/",
      taxiRide, String.class);

    assertThat(fare, equalTo("200"));
}

7. 結論

この記事では、インターセプターを使用して基本的なWeb要求ロギングを実装する方法を示しました。また、このソリューションの制限と課題も示しました。

次に、すぐに使用できる簡単なログメカニズムを提供する組み込みフィルタークラスを示しました。

いつものように、例とコードスニペットの実装は利用可能ですover on GitHub.