Jak poprawnie logować żądania http za pomocą Spring MVC

Witam próbuję wymyślić ogólny sposób logowania żądań http w mojej aplikacji, do tej pory bez powodzenia, oto jak sobie teraz radzę z logowaniem tj.:

@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;
    }

Więc to, co robię teraz dla różnych kontrolerów / metod, to kopiowanie wszystkiego od początku wnętrza metody do logiki przetwarzania, która różni się od metody do metody, a następnie kopiowanie wszystkiego od dołu, jak pokazano w powyższym szablonie.

JEST Trochę bałagan, i jest dużo kodu powtórzenie (co mi się nie podoba). Ale muszę wszystko zapisać.

Czy ktoś ma większe doświadczenie z tego typu logowaniem, czy ktoś może rzucić na to trochę światła?

Author: Balder, 2011-07-09

6 answers

Użyj :

  • extend HandlerInterceptorAdapter and override preHandle
  • zdefiniuj go za pomocą <mvc:interceptors> w dispatcher-servlet.xml

Będzie działać dla każdego żądania.

 25
Author: Bozho,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-08 22:38:40

EDIT: Zobacz także komentarz @ membersound na temat tej odpowiedzi, który poprawia tę odpowiedź.

Wiosna to wspiera. Zobacz CommonsRequestLoggingFilter . Jeśli używasz Spring Boot, wystarczy zarejestrować fasolę tego typu, A Boot zastosuje ją do łańcucha filtrów. Like:

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

Również ten filtr logowania wymaga ustawienia poziomu logowania na debugowanie. Np. zrób to w logbacku.XML z:

<logger name="org.springframework.web.filter.CommonsRequestLoggingFilter" level="DEBUG"/>
 43
Author: dgtc,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-01 21:56:27

Główny problem z żądaniem odczytu polega na tym, że gdy tylko strumień wejściowy zostanie zużyty, jego strumień zniknie... i nie można go ponownie przeczytać. Więc strumień wejściowy musi być buforowany. Zamiast pisać własne klasy do buforowania (które można znaleźć w kilku miejscach w sieci), Spring dostarcza kilka przydatnych klas, np. ContentCachingRequestWrapperi ContentCachingResponseWrapper. Klasy te mogą być wykorzystywane bardzo efektywnie, na przykład w filtrach do logowania cele.

Zdefiniuj filtr w sieci.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>

Ponieważ filtr jest zadeklarowany jako DelegatingFilterProxy, może być zadeklarowany jako bean za pomocą adnotacji @Component lub @Bean. W metodzie dofilter loggingFilter, owiń żądanie i odpowiedź klasami dostarczonymi przez sprężynę przed przekazaniem jej do łańcucha filtrów:

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

Strumień wejściowy będzie buforowany w zawiniętym żądaniu, gdy tylko strumień wejściowy zostanie zużyty po łańcuchu.doFilter (). Następnie można go uzyskać jako poniżej:

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;
}

Jednakże, sprawy są nieco inne dla odpowiedzi. Ponieważ odpowiedź została również owinięta przed przekazaniem jej do łańcucha filtrów, będzie ona również buforowana do strumienia wyjściowego, gdy tylko zostanie zapisana w drodze powrotnej. Ale ponieważ strumień wyjściowy będzie również zużywany, więc musisz skopiować odpowiedź z powrotem do strumienia wyjściowego za pomocą wrappera.copyBodyToResponse (). Patrz poniżej:

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;
}
Mam nadzieję, że to pomoże!
 7
Author: B. Ali,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-11 07:02:25

Oto mała biblioteka, którą napisałem możesz użyć: spring-mvc-logger

Udostępniłem go przez Maven central:

<dependency>
    <groupId>com.github.isrsal</groupId>
    <artifactId>spring-mvc-logger</artifactId>
    <version>0.2</version>
</dependency>
 5
Author: Israel Zalmanov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-22 00:29:41

Dodając do tego, na co odpowiedział @B. Ali. Jeśli używasz tego w scenariuszu obsługi spring asynchronous request (serlvet 3.0 lub nowszy), następujący kod działa dla mnie.

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
Author: Abe,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-08-22 19:03:21

Jak każda odpowiedź techniczna ... to zależy .. na stosie technologicznym, którego używasz i jakie są Twoje wymagania.

Na przykład im bardziej ogólne chcesz dokonać logowania, tym bardziej z góry chcesz to zrobić. w Twoim przypadku rejestrujesz tylko żądania, które są włączone i są obsługiwane w kontekście spring. Więc możesz "przegapić" inne prośby.

Spojrzałbym na kontener lub serwer WWW, którego używasz do uruchamiania aplikacji. To usunie to uzależnienie od wiosny. Kontenery Plus zapewniają elastyczność podłączania dostawcy rejestrowania, a następnie konfigurowania formatu kodu dziennika poza. Na przykład, jeśli używasz serwera WWW Apache, Użyj protokołu Apache web server logging, aby zarejestrować wszystkie żądania HTTP w warstwie logowania dostępu. Ale uważaj, niektóre opcje logowania mają kary za wydajność. Rejestruj tylko to, czego naprawdę potrzebujesz, aby uzyskać perspektywę monitorowania wzorca dostępu.

Jeśli używasz tomcat, to tomcat również pozwoli Ci rejestrować rzeczy. Wyszukaj Zawór dostępu w dokumentacji programu tomcat dla używanego programu tomcat. To otworzy świat możliwości.

Bardziej rozbudowane logowanie powinno być domeną strategii wyjątków, czyli tego rodzaju detali, które chcesz zobaczyć, gdy wystąpi problem w systemie.

 1
Author: Master Chief,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-07-09 00:38:28