Printemps - Journal des demandes entrantes

Printemps - Journal des demandes entrantes

1. introduction

Dans ce rapide tutoriel, nous allons montrer les bases de la journalisation des requêtes entrantes à l'aide du filtre de journalisation de Spring. Si vous ne faites que commencer avec la journalisation, consultez ceslogging intro article, ainsi que lesSLF4J article.

2. Dépendances Maven

Les dépendances de journalisation seront simplement les mêmes que celles de l'article d'introduction; ajoutons simplement Spring ici:


    org.springframework
    spring-core
    5.0.5.RELEASE

La dernière version peut être trouvée ici pourspring-core.

3. Contrôleur Web de base

Tout d’abord, définissons un contrôleur qui sera utilisé dans notre exemple:

@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. Journalisation des demandes personnalisées

Spring fournit un mécanisme permettant de configurer des intercepteurs définis par l'utilisateur pour effectuer des actions avant et après les requêtes Web.

Parmi les intercepteurs de requêtes Spring, l'une des interfaces remarquables estHandlerInterceptor, qui peut être utilisée pour enregistrer la requête entrante en implémentant les méthodes suivantes:

  1. preHandle() – cette méthode est exécutée avant la méthode de service du contrôleur réel

  2. afterCompletion() – cette méthode est exécutée une fois que le contrôleur est prêt à envoyer la réponse

De plus, Spring fournit l'implémentation par défaut de l'interfaceHandlerInterceptor sous la forme de la classeHandlerInterceptorAdaptor qui peut être étendue par l'utilisateur.

Créons notre propre intercepteur - en étendantHandlerInterceptorAdaptor 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) {
        //
    }
}

Enfin, nous allons configurer lesTaxiRideRequestInterceptor à l'intérieur du cycle de vie MVC pour capturer le pré et le post-traitement des appels de méthode de contrôleur qui sont mappés aux chemins/taxifare définis dans la classeTaxiFareController.

@Configuration
public class TaxiFareMVCConfig implements WebMvcConfigurer {

    @Autowired
    private TaxiFareRequestInterceptor taxiFareRequestInterceptor;

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

En conclusion, leWebMvcConfigurer ajoute leTaxiFareRequestInterceptor à l'intérieur du cycle de vie MVC du ressort en invoquant la méthodeaddInterceptors().

Le plus gros défi consiste à obtenir les copies des données utiles de la demande et de la réponse pour la journalisation tout en laissant la charge utile demandée au servlet pour le traiter.

Le principal problème avec la demande de lecture est que, dès que le flux d'entrée est lu pour la première fois, il est marqué comme consommé et ne peut plus être lu.

L'application lève une exception après avoir lu le flux de demandes:

{
  "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, nous pouvons tirer parti de la mise en cache pour stocker le flux de requête et l'utiliser pour la journalisation.

Spring fournit quelques classes utiles telles queContentCachingRequestWrapper etContentCachingResponseWrapper qui peuvent être utilisées pour la mise en cache des données de requête à des fins de journalisation.

Ajustez nospreHandle() de la classeTaxiRideRequestInterceptor pour mettre en cache l'objet de requête en utilisant la classeContentCachingRequestWrapper.

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

Comme nous pouvons le voir, nous avons mis en cache l'objet de requête en utilisant la classeContentCachingRequestWrapper qui peut être utilisée pour lire les données de charge utile pour la journalisation sans déranger l'objet de requête réel:

requestCacheWrapperObject.getContentAsByteArray();

Limitation

  • La classeContentCachingRequestWrapper prend uniquement en charge les éléments suivants:

Content-Type:application/x-www-form-urlencoded
Method-Type:POST
  • Nous devons appeler la méthode suivante pour nous assurer que les données de la requête sont mises en cache dansContentCachingRequestWrapper avant de les utiliser:

requestCacheWrapperObject.getParameterMap();

5. Journalisation des requêtes intégrée Spring

Spring fournit une solution intégrée pour journaliser les charges utiles. Nous pouvons utiliser des filtres prêts à l'emploi en les connectant à l'application Spring à l'aide de la configuration.

AbstractRequestLoggingFilter est un filtre qui fournit des fonctions de base de journalisation. Les sous-classes doivent remplacer les méthodesbeforeRequest() etafterRequest() pour effectuer la journalisation réelle autour de la demande.

Le framework Spring fournit trois classes d'implémentation concrètes pouvant être utilisées pour consigner la demande entrante. Ces trois classes sont:

  • CommonsRequestLoggingFilter

  • Log4jNestedDiagnosticContextFilter (obsolète)

  • ServletContextRequestLoggingFilter

Passons maintenant auxCommonsRequestLoggingFilter et configurons-les pour capturer les demandes entrantes de journalisation.

5.1. Configurer l'application Spring Boot

L'application Spring Boot peut être configurée en ajoutant une définition de bean pour activer la journalisation des demandes:

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

De plus, ce filtre de journalisation nécessite que le niveau de journalisation soit défini sur DEBUG. Nous pouvons activer le mode DEBUG en ajoutant l'élément ci-dessous danslogback.xml:


    

Une autre façon d'activer le journal de niveau DEBUG consiste à ajouter les éléments suivants dansapplication.properties:

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

5.2. Configurer l'application Web traditionnelle

Dans l'application Web Spring standard,Filter peut être défini via la configuration XML ou la configuration Java. Configurons lesCommonsRequestLoggingFilter en utilisant une configuration Java classique.

Comme nous le savons, l'attributincludePayload deCommonsRequestLoggingFilter est défini sur false par défaut. Nous aurions besoin d'une classe personnalisée pour remplacer la valeur de l'attribut pour activerincludePayload avant d'injecter dans le conteneur en utilisant la configuration Java:

public class CustomeRequestLoggingFilter
  extends CommonsRequestLoggingFilter {

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

Maintenant, nous devons injecter lesCustomeRequestLoggingFilter en utilisantJava 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. Exemple en action

Maintenant, nous pouvons connecter un démarrage Spring avec un contexte et voir en action que la journalisation des demandes entrantes fonctionne comme prévu:

@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. Conclusion

Dans cet article, nous avons montré comment implémenter la journalisation de base de requêtes Web à l'aide d'intercepteurs; Nous avons également montré les limites et les défis de cette solution.

Nous avons ensuite présenté la classe de filtre intégrée, qui fournit un mécanisme de journalisation simple à utiliser et prêt à l'emploi.

Comme toujours, l'implémentation de l'exemple et des extraits de code sont disponiblesover on GitHub.