Как правильно протоколировать http запросы с помощью Spring MVC - PullRequest
38 голосов
/ 09 июля 2011

Здравствуйте, я пытался найти общий способ регистрации 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;
    }

Так что ясделать прямо сейчас для разных контроллеров / методов это скопировать все от начала внутри метода до логики обработки, которая отличается от метода к методу, а затем скопировать все снизу, как показано в шаблоне выше.

Этоэто немного грязно, и много повторений кода (что мне не нравится).Но мне нужно регистрировать все.

У кого-нибудь есть больше опыта с этими видами регистрации, кто-нибудь может пролить свет на это?

Ответы [ 6 ]

44 голосов
/ 04 июня 2015

РЕДАКТИРОВАТЬ: Также см. Комментарий @ 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"/>
26 голосов
/ 09 июля 2011

Используйте перехватчик :

  • , расширьте HandlerInterceptorAdapter и переопределите preHandle
  • , определите его с помощью <mvc:interceptors> в dispatcher-servlet.xml

Он будет выполняться для каждого запроса.

7 голосов
/ 11 августа 2016

Основная проблема с запросом на чтение заключается в том, что, как только входной поток потребляется, он исчезает ... и не может быть прочитан снова. Поэтому входной поток должен быть кэширован. Вместо написания ваших собственных классов для кэширования (которые можно найти в нескольких местах в Интернете), 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. В методе doFilter loggingFilter оберните запрос и ответ классами, предоставленными пружиной, прежде чем передавать его в цепочку фильтров:

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

Надеюсь, это поможет!

6 голосов
/ 05 декабря 2013

Вот небольшая библиотека, которую я написал, которую вы можете использовать: spring-mvc-logger

Я сделал ее доступной через maven central:

<dependency>
    <groupId>com.github.isrsal</groupId>
    <artifactId>spring-mvc-logger</artifactId>
    <version>0.2</version>
</dependency>
2 голосов
/ 20 июля 2017

Добавление к тому, что @ 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.
}

}

1 голос
/ 09 июля 2011

Как и любой технический ответ ... это зависит .. в техническом стеке, который вы используете, и каковы ваши требования.

например, чем более обобщенно вы хотите сделать ведение журнала, тем дальше вы захотите это сделать. в вашем случае вы регистрируете только те запросы, которые включены и обрабатываются в контексте Spring. Таким образом, вы можете «пропустить» другие запросы.

Я бы посмотрел на контейнер или веб-сервер, который вы используете для запуска вашего приложения. Это уберет эту зависимость от Spring. Контейнеры Plus обеспечивают гибкость подключения поставщика журналов и последующей настройки формата журнала вне кода. Например, если вы используете веб-сервер Apache, используйте ведение журнала веб-сервера Apache для регистрации всех HTTP-запросов на уровне ведения журнала доступа. Но будьте осторожны, некоторые из опций регистрации имеют потери производительности. Внесите в журнал только то, что вам действительно нужно для перспективы мониторинга шаблонов доступа.

Если вы используете tomcat, то tomcat также позволит вам регистрировать вещи. Найдите Access Valve в документации по tomcat для используемого вами кота. Это откроет мир возможностей.

Более обширное ведение журнала должно быть предметом стратегии исключения, то есть того типа деталей, который вы хотите видеть при возникновении проблемы в системе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...