Spring - Journaliser les demandes entrantes

1. Introduction

Dans ce tutoriel rapide, nous allons montrer les bases de la journalisation des demandes entrantes à l'aide du filtre de journalisation de Spring. Si vous ne faites que commencer avec la journalisation, consultez cet article d'introduction à la journalisation, ainsi que l'article SLF4J.

2. Dépendances de 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.2.2.RELEASE 

La dernière version peut être trouvée ici pour spring-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 de configuration d'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 est HandlerInterceptor , qui peut être utilisée pour journaliser 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

En outre, Spring fournit l'implémentation par défaut de l' interface HandlerInterceptor sous la forme de la classe HandlerInterceptorAdaptor qui peut être étendue par l'utilisateur.

Créons notre propre intercepteur - en étendant HandlerInterceptorAdaptor comme:

@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 le TaxiRideRequestInterceptor dans le cycle de vie MVC pour capturer le pré et le post-traitement des appels de méthode de contrôleur qui sont mappés au chemin / taxifare défini dans la classe TaxiFareController .

@Configuration public class TaxiFareMVCConfig implements WebMvcConfigurer { @Autowired private TaxiFareRequestInterceptor taxiFareRequestInterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(taxiFareRequestInterceptor) .addPathPatterns("/**/taxifare/**/"); } }

En conclusion, le WebMvcConfigurer ajoute le TaxiFareRequestInterceptor à l' intérieur du cycle de vie Spring MVC en appelant la méthode addInterceptors () .

Le plus grand défi est d'obtenir les copies de la charge utile de demande et de réponse pour la journalisation et de laisser la charge utile demandée au servlet pour la 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èvera une exception après avoir lu le flux de requête:

{ "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/" }

Pour surmonter ce problème , nous pouvons tirer parti de la mise en cache pour stocker le flux de demande et l'utiliser pour la journalisation.

Spring fournit quelques classes utiles telles que ContentCachingRequestWrapper et ContentCachingResponseWrapper qui peuvent être utilisées pour la mise en cache des données de demande à des fins de journalisation.

Ajustez notre preHandle () de la classe TaxiRideRequestInterceptor pour mettre en cache l'objet de requête à l'aide de la classe 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; }

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

requestCacheWrapperObject.getContentAsByteArray();

Limitation

  • La classe ContentCachingRequestWrapper 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 demande sont mises en cache dans ContentCachingRequestWrapper avant de l'utiliser:
requestCacheWrapperObject.getParameterMap();

5. Journalisation des requêtes intégrée au printemps

Spring fournit une solution intégrée pour journaliser les charges utiles. Nous pouvons utiliser des filtres prêts à l'emploi en nous 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éthodes beforeRequest () et afterRequest () pour effectuer la journalisation réelle autour de la requête.

Spring Framework fournit trois classes d'implémentation concrètes qui peuvent être utilisées pour journaliser la demande entrante. Ces trois classes sont:

  • CommonsRequestLoggingFilter
  • Log4jNestedDiagnosticContextFilter (obsolète)
  • ServletContextRequestLoggingFilter

Passons maintenant à CommonsRequestLoggingFilter et configurons-le pour capturer la demande entrante 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; } }

En outre, 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 dans logback.xml :

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

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

5.2. Configurer l'application Web traditionnelle

In the standard Spring web application, Filter can be set via either XML configuration or Java configuration. Let's set up the CommonsRequestLoggingFilter using conventional Java based configuration.

As we know, the includePayload attribute of CommonsRequestLoggingFilter is set to false by default. We would need a custom class to override the value of the attribute to enable includePayload before injecting into the container using Java configuration:

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

Now, we need to inject the CustomeRequestLoggingFilter using Java based web initializer:

public class CustomWebAppInitializer implements WebApplicationInitializer { public void onStartup(ServletContext container) { AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext(); context.setConfigLocation("com.baeldung"); 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. Example in Action

Now, we can wire up a Spring Boot with context and see in action that logging of incoming requests works as expected:

@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 des requêtes Web de base à l'aide d'intercepteurs; nous avons également montré les limites et les défis de cette solution.

Ensuite, nous avons montré la classe de filtre intégrée qui fournit un mécanisme de journalisation simple et prêt à l'emploi.

Comme toujours, l'implémentation de l'exemple et les extraits de code sont disponibles à l'adresse over sur GitHub.