web-dev-qa-db-ja.com

Spring MVCでhttpリクエストを適切に記録する方法

こんにちは私は私のアプリケーションでhttpリクエストをログに記録する一般的な方法を見つけようとしてきましたが、これまでのところ運はありません。ここで私が今ログを処理する方法です:

@RequestMapping(value="register", method = RequestMethod.POST)
    @ResponseBody
    public String register(@RequestParam(value="param1",required=false) String param1, @RequestParam("param2") String param2, @RequestParam("param3") String param3, HttpServletRequest request){
        long start = System.currentTimeMillis();
        logger.info("!--REQUEST START--!");

        logger.info("Request URL: " + request.getRequestURL().toString());

        List<String> requestParameterNames = Collections.list((Enumeration<String>)request.getParameterNames());
        logger.info("Parameter number: " + requestParameterNames.size()); 

 for (String parameterName : requestParameterNames){
           logger.info("Parameter name: " + parameterName + " - Parameter value: " + request.getParameter(parameterName));
        }
                  //Some processing logic, call to the various services/methods with different parameters, response is always String(Json)
        String response = service.callSomeServiceMethods(param1,param2,param3);

logger.info("Response is: " + response);

        long end = System.currentTimeMillis();
        logger.info("Requested completed in: " + (end-start) + "ms");
        logger.info("!--REQUEST END--!");   

        return response;
    }

異なるコントローラー/メソッドに対して今やっていることは、メソッドの内部の最初からメソッドごとに異なる処理ロジックまですべてをコピーしてから、上記のテンプレートに示すようにその下からすべてをコピーすることです。

それはちょっと厄介で、多くのコードの繰り返しがあります(私は好きではありません)。しかし、私はすべてを記録する必要があります。

誰もこの種類のロギングの経験がありますか、誰かこれに光を当てることができますか?

38
London

インターセプター を使用します。

  • HandlerInterceptorAdapterを拡張し、preHandleをオーバーライドします
  • <mvc:interceptors>dispatcher-servlet.xmlで定義する

すべてのリクエストに対して実行されます。

26
Bozho

編集:また、この回答に関する@membersoundのコメントを参照してください。これにより、この回答が改善されます。

Springはこれをサポートしています。 CommonsRequestLoggingFilter を参照してください。 Spring Bootを使用している場合、そのタイプのBeanを登録するだけで、Bootはそれをフィルターチェーンに適用します。お気に入り:

@Bean
public Filter logFilter() {
    CommonsRequestLoggingFilter filter = new CommonsRequestLoggingFilter();
    filter.setIncludeQueryString(true);
    filter.setIncludePayload(true);
    filter.setMaxPayloadLength(5120);
    return filter;
}

また、このログフィルタでは、ログレベルをDEBUGに設定する必要があります。例えば。 logback.xmlで次を実行します。

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

読み取り要求の主な問題は、入力ストリームが消費されるとすぐに、誰が...再び読み取れないことです。そのため、入力ストリームをキャッシュする必要があります。キャッシング用の独自のクラス(Webの複数の場所にあります)を記述する代わりに、Springはいくつかの便利なクラス、つまり ContentCachingRequestWrapper および ContentCachingResponseWrapper を提供します。これらのクラスは、たとえばログ記録用のフィルターで非常に効果的に使用できます。

Web.xmlでフィルターを定義します。

<filter>
    <filter-name>loggingFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
    <filter-name>loggingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

フィルターはDelegatingFilterProxyとして宣言されているため、@ Componentまたは@Beanアノテーションを使用してBeanとして宣言できます。 loggingFilterのdoFilterメソッドで、リクエストとレスポンスをスプリングで提供されるクラスでラップしてから、フィルターチェーンに渡します。

HttpServletRequest requestToCache = new ContentCachingRequestWrapper(request);
HttpServletResponse responseToCache = new ContentCachingResponseWrapper(response);
chain.doFilter(requestToCache, responseToCache);
String requestData = getRequestData(requestToCache);
String responseData = getResponseData(responseToCache);

入力ストリームは、chain.doFilter()の後に入力ストリームが消費されるとすぐに、ラップされた要求にキャッシュされます。次に、以下のようにアクセスできます。

public static String getRequestData(final HttpServletRequest request) throws UnsupportedEncodingException {
    String payload = null;
    ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
    if (wrapper != null) {
        byte[] buf = wrapper.getContentAsByteArray();
        if (buf.length > 0) {
            payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
        }
    }
    return payload;
}

ただし、対応については少し異なります。応答もフィルターチェーンに渡す前にラップされているため、途中で書き込まれるとすぐに出力ストリームにキャッシュされます。ただし、出力ストリームも消費されるため、wrapper.copyBodyToResponse()を使用して応答を出力ストリームにコピーする必要があります。下記参照:

public static String getResponseData(final HttpServletResponse response) throws IOException {
    String payload = null;
    ContentCachingResponseWrapper wrapper =
        WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
    if (wrapper != null) {
        byte[] buf = wrapper.getContentAsByteArray();
        if (buf.length > 0) {
            payload = new String(buf, 0, buf.length, wrapper.getCharacterEncoding());
            wrapper.copyBodyToResponse();
        }
    }
    return payload;
}

それが役に立てば幸い!

7
B. Ali

以下に、使用できる小さなライブラリを示します。 spring-mvc-logger

私はそれをMaven Central経由で利用可能にしました:

<dependency>
    <groupId>com.github.isrsal</groupId>
    <artifactId>spring-mvc-logger</artifactId>
    <version>0.2</version>
</dependency>
6
Israel Zalmanov

@ B.ALiが答えたことに追加します。春の非同期リクエスト(serlvet 3.0以降)処理シナリオでこれを使用している場合、次のコードが私のために働いたものです。

public class OncePerRequestLoggingFilter extends OncePerRequestFilter {

@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    boolean isFirstRequest = !isAsyncDispatch(request);
    HttpServletRequest requestToUse = request;
    HttpServletResponse responseToUse = response;

    // The below check is critical and if not there, then the request/response gets corrupted.
    // Probably because in async case the filter is invoked multiple times.
    if (isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
        requestToUse = new ContentCachingRequestWrapper(request);
    }

    if (isFirstRequest && !(response instanceof ContentCachingResponseWrapper)) {
        responseToUse = new ContentCachingResponseWrapper(response);
    }

    filterChain.doFilter(requestToUse, responseToUse);

    if (!isAsyncStarted(request)) {
        ContentCachingResponseWrapper responseWrapper =
                WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        responseWrapper.copyBodyToResponse(); // IMPORTANT to copy it back to response
    }
}

@Override
protected boolean shouldNotFilterAsyncDispatch() {
    return false; // IMPORTANT this is true by default and wont work in async scenario.
}

}

2
Abe

技術的な答えとして... .....使用している技術スタックと要件に依存します。

たとえば、ロギングをより汎用的にしたい場合は、さらに事前に行います。あなたの場合、あなたは、ロギングが有効になっていて、春のコンテキストで処理されているリクエストのみをロギングしています。したがって、他のリクエストが「欠落」している可能性があります。

アプリの実行に使用しているコンテナまたはWebサーバーを確認します。これにより、Springへのこの依存関係が削除されます。 Plusコンテナを使用すると、ログプロバイダーに接続して、外部コードのログの形式を構成する柔軟性が得られます。たとえば、Apache Webサーバーを使用している場合は、Apache Webサーバーのログを使用して、すべてのHTTP要求をアクセスログレイヤーに記録します。ただし、一部のロギングオプションにはパフォーマンスが低下するという注意が必要です。アクセスパターンモニタリングの観点から真剣に必要なものだけを記録します。

Tomcatを使用している場合は、Tomcatでもログを記録できます。使用しているTomcatのTomcatドキュメントでAccess Valveを検索します。それは可能性の世界を開きます。

より広範なロギングは、例外戦略のドメイン、つまり、システムで問題が発生したときに表示する詳細の種類にする必要があります。

1
Master Chief