Spring - Журнал входящих запросов

Весна - Вход Входящие Запросы

1. Вступление

В этом кратком руководстве мы покажем основы регистрации входящих запросов с помощью фильтра ведения журнала Spring. Если вы только начинаете вести журнал, обратите внимание на этотlogging intro article, а также наSLF4J article.

2. Maven Зависимости

Зависимости журналирования будут просто такими же, как и во вводной статье; давайте просто добавим сюда Spring:


    org.springframework
    spring-core
    5.0.5.RELEASE

Последнюю версию можно найти здесь дляspring-core.

3. Базовый веб-контроллер

Прежде всего, давайте определим контроллер, который будет использоваться в нашем примере:

@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 предоставляет механизм для настройки пользовательских перехватчиков для выполнения действий до и после веб-запросов.

Среди перехватчиков запросов Spring одним из примечательных интерфейсов являетсяHandlerInterceptor, который можно использовать для регистрации входящего запроса путем реализации следующих методов:

  1. preHandle() – этот метод выполняется перед фактическим методом обслуживания контроллера

  2. afterCompletion() – этот метод выполняется после того, как контроллер готов отправить ответ

Кроме того, Spring предоставляет стандартную реализацию интерфейсаHandlerInterceptor в виде классаHandlerInterceptorAdaptor, который может быть расширен пользователем.

Давайте создадим наш собственный перехватчик - расширив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) {
        //
    }
}

Наконец, мы настроимTaxiRideRequestInterceptor внутри жизненного цикла MVC для захвата предварительной и пост-обработки вызовов методов контроллера, которые отображаются на путь/taxifare, определенный в классеTaxiFareController.

@Configuration
public class TaxiFareMVCConfig implements WebMvcConfigurer {

    @Autowired
    private TaxiFareRequestInterceptor taxiFareRequestInterceptor;

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

В заключение,WebMvcConfigurer добавляетTaxiFareRequestInterceptor внутри жизненного цикла Spring MVC, вызывая методaddInterceptors().

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

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

Приложение сгенерирует исключение после прочтения потока запроса:

{
  "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 предоставляет несколько полезных классов, таких какContentCachingRequestWrapper иContentCachingResponseWrapper, которые можно использовать для кэширования данных запроса для целей ведения журнала.

Давайте настроим нашpreHandle() классаTaxiRideRequestInterceptor для кеширования объекта запроса с использованием класса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 предоставляет три конкретных класса реализации, которые можно использовать для регистрации входящего запроса. Эти три класса:

  • CommonsRequestLoggingFilter

  • Log4jNestedDiagnosticContextFilter (устарело)

  • ServletContextRequestLoggingFilter

Теперь перейдем кCommonsRequestLoggingFilter и настроим его для захвата входящего запроса для регистрации.

5.1. Настроить приложение Spring Boot

Приложение Spring Boot можно настроить, добавив определение компонента для включения ведения журнала запросов:

@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. Мы можем включить режим DEBUG, добавив следующий элемент вlogback.xml:


    

Другой способ включить журнал уровня DEBUG - добавить вapplication.properties следующее:

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

5.2. Настроить традиционное веб-приложение

В стандартном веб-приложении SpringFilter можно установить с помощью конфигурации XML или конфигурации Java. Давайте настроимCommonsRequestLoggingFilter, используя обычную конфигурацию на основе Java.

Как мы знаем, для атрибутаincludePayloadCommonsRequestLoggingFilter по умолчанию установлено значение false. Нам понадобится настраиваемый класс, чтобы переопределить значение атрибута для включенияincludePayload перед внедрением в контейнер с использованием конфигурации Java:

public class CustomeRequestLoggingFilter
  extends CommonsRequestLoggingFilter {

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

Теперь нам нужно ввестиCustomeRequestLoggingFilter, используяJava based web initializer:

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. Заключение

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

Затем мы показали встроенный класс фильтра, который обеспечивает готовый к использованию и простой механизм регистрации.

Как всегда, доступны реализация примера и фрагменты кодаover on GitHub.

Related