1. 序章

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

2. Mavenの依存関係

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

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.2.2.RELEASE</version>   
</dependency>

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は、 HandlerInterceptor インターフェイスのデフォルトの実装を、ユーザーが拡張できるHandlerInterceptorAdaptorクラスの形式で提供します。

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

@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()メソッドを呼び出すことにより、TaxiFareRequestInterceptorを春のMVCライフサイクル内に追加します。

最大の課題は、ロギング用の要求と応答のペイロードのコピーを取得し、それでもサーブレットが処理するために要求されたペイロードを残すことです。

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

アプリケーションは、リクエストストリームを読み取った後、例外をスローします。

{
  "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/"
}

この問題を克服するために、キャッシュを利用してリクエストストリームを保存し、ログに使用できます。

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

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

@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 Frameworkは、着信要求をログに記録するために使用できる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モードを有効にできます。

<logger name="org.springframework.web.filter.CommonsRequestLoggingFilter">
    <level value="DEBUG" />
</logger>

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ベースのWeb初期化子を使用してCustomeRequestLoggingFilterを挿入する必要があります。

public class CustomWebAppInitializer implements 
  WebApplicationInitializer {
    public void onStartup(ServletContext container) {
        
        AnnotationConfigWebApplicationContext context 
          = new AnnotationConfigWebApplicationContext();
	context.setConfigLocation("com.baeldung");
	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要求ロギングを実装する方法を示しました。 また、このソリューションの制限と課題も示しました。

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

いつものように、サンプルとコードスニペットの実装はGitHubで利用できます。