Lente - Log inkomende verzoeken

1. Inleiding

In deze korte zelfstudie laten we de basisprincipes zien van het loggen van inkomende verzoeken met het logboekfilter van Spring. Als je net begint met loggen, bekijk dan dit intro-artikel over loggen, evenals het SLF4J-artikel.

2. Maven afhankelijkheden

De afhankelijkheden van de logboekregistratie zullen gewoon dezelfde zijn als die in het intro-artikel; laten we hier gewoon de lente toevoegen:

 org.springframework spring-core 5.2.2.RELEASE 

De laatste versie is hier te vinden voor spring-core.

3. Basiswebcontroller

Laten we allereerst een controller definiëren die in ons voorbeeld zal worden gebruikt:

@RestController openbare klasse TaxiFareController {@GetMapping ("/ taxifare / get /") openbare RateCard getTaxiFare () {retourneer nieuwe RateCard (); } @PostMapping ("/ taxifare / bereken /") public String berekenTaxiFare (@RequestBody @Valid TaxiRide taxiRide) {// retourneer het berekende tarief}}

4. Logboekregistratie van aangepaste verzoeken

Spring biedt een mechanisme voor het configureren van door de gebruiker gedefinieerde interceptors om acties uit te voeren voor en na webverzoeken.

Onder de Spring request-interceptors is een van de opmerkelijke interfaces HandlerInterceptor, die kan worden gebruikt om het inkomende verzoek te loggen door de volgende methoden te implementeren:

  1. preHandle () - deze methode wordt uitgevoerd vóór de feitelijke controller-servicemethode
  2. na voltooiing() - deze methode wordt uitgevoerd nadat de controller klaar is om het antwoord te verzenden

Bovendien biedt Spring de standaardimplementatie van HandlerInterceptor interface in de vorm van HandlerInterceptorAdaptor klasse die kan worden uitgebreid door de gebruiker.

Laten we onze eigen interceptor creëren - door uit te breiden HandlerInterceptorAdaptor net zo:

@Component openbare klasse TaxiFareRequestInterceptor breidt HandlerInterceptorAdapter uit {@Override openbare boolean preHandle (verzoek HttpServletRequest, antwoord HttpServletResponse, objecthandler) {return true; } @Override public void afterCompletion (HttpServletRequest-verzoek, HttpServletResponse-antwoord, Objecthandler, Exception ex) {//}}

Ten slotte configureren we het TaxiRideRequestInterceptor binnen MVC-levenscyclus om pre- en postverwerking van aanroepen van controllermethoden vast te leggen die aan het pad zijn toegewezen / taxifare gedefinieerd in TaxiFareController klasse.

@Configuration openbare klasse TaxiFareMVCConfig implementeert WebMvcConfigurer {@Autowired privé TaxiFareRequestInterceptor taxiFareRequestInterceptor; @Override public void addInterceptors (InterceptorRegistry-register) {registry.addInterceptor (taxiFareRequestInterceptor) .addPathPatterns ("/ ** / taxifare / ** /"); }}

Tot slot, de WebMvcConfigurer voegt de TaxiFareRequestInterceptor inside spring MVC lifecycle door een beroep te doen op addInterceptors () methode.

De grootste uitdaging is om de kopieën van de verzoek- en respons-payload voor logboekregistratie te krijgen en toch de gevraagde payload over te laten zodat de servlet deze kan verwerken.

Het belangrijkste probleem met het leesverzoek is dat zodra de invoerstroom voor de eerste keer wordt gelezen, deze wordt gemarkeerd als verbruikt en niet opnieuw kan worden gelezen.

De applicatie genereert een uitzondering na het lezen van de verzoekstroom:

{"timestamp": 1500645243383, "status": 400, "error": "Bad Request", "exception": "org.springframework.http.converter .HttpMessageNotReadableException", "message": "Kon document niet lezen: stream gesloten ; geneste uitzondering is java.io.IOException: Stream gesloten "," pad ":" / rest-log / taxifare / bereken / "}

Om dit probleem te verhelpenkunnen we caching gebruiken om de verzoekstroom op te slaan en te gebruiken voor logboekregistratie.

Spring biedt enkele nuttige klassen zoals ContentCachingRequestWrapper en ContentCachingResponseWrapper die kunnen worden gebruikt voor het cachen van de verzoekgegevens voor logboekdoeleinden.

Laten we onze aanpassen preHandle () van TaxiRideRequestInterceptor class om het request-object te cachen met ContentCachingRequestWrapper klasse.

@Override openbare boolean preHandle (verzoek HttpServletRequest, antwoord HttpServletResponse, objecthandler) {HttpServletRequest requestCacheWrapperObject = nieuwe ContentCachingRequestWrapper (verzoek); requestCacheWrapperObject.getParameterMap (); // Lees inputStream van requestCacheWrapperObject en log het als resultaat true; }

Zoals we kunnen zien, hebben we het request-object in de cache opgeslagen met ContentCachingRequestWrapper klasse die kan worden gebruikt om de payload-gegevens voor logboekregistratie te lezen zonder het eigenlijke verzoekobject te verstoren:

requestCacheWrapperObject.getContentAsByteArray ();

Beperking

  • ContentCachingRequestWrapper class ondersteunt alleen het volgende:
Inhoudstype: application / x-www-form-urlencoded Method-Type: POST
  • We moeten de volgende methode gebruiken om ervoor te zorgen dat verzoekgegevens in de cache worden opgeslagen ContentCachingRequestWrapper voordat u het gebruikt:
requestCacheWrapperObject.getParameterMap ();

5. In de lente ingebouwde logboekregistratie

Spring biedt een ingebouwde oplossing om payloads te loggen. We kunnen kant-en-klare filters gebruiken door in te pluggen in de Spring-applicatie met behulp van configuratie.

AbstractRequestLoggingFilter is een filter dat basisfuncties voor logboekregistratie biedt. Subklassen moeten de beforeRequest () en afterRequest () methoden om de daadwerkelijke logging rond het verzoek uit te voeren.

Spring Framework biedt drie concrete implementatieklassen die kunnen worden gebruikt om het inkomende verzoek te loggen. Deze drie klassen zijn:

  • CommonsRequestLoggingFilter
  • Log4jNestedDiagnosticContextFilter (verouderd)
  • ServletContextRequestLoggingFilter

Laten we nu verder gaan met de CommonsRequestLoggingFilter en configureer het om inkomende verzoeken voor logboekregistratie vast te leggen.

5.1. Configureer Spring Boot Application

Spring Boot-applicatie kan worden geconfigureerd door een bean-definitie toe te voegen om logboekregistratie van aanvragen mogelijk te maken:

@Configuration openbare klasse RequestLoggingFilterConfig {@Bean openbare CommonsRequestLoggingFilter logFilter () {CommonsRequestLoggingFilter filter = nieuwe CommonsRequestLoggingFilter (); filter.setIncludeQueryString (true); filter.setIncludePayload (true); filter.setMaxPayloadLength (10000); filter.setIncludeHeaders (false); filter.setAfterMessagePrefix ("VERZOEK GEGEVENS:"); retourfilter; }}

Dit logboekfilter vereist ook dat het logniveau wordt ingesteld op DEBUG. We kunnen de DEBUG-modus inschakelen door het onderstaande element toe te voegen in logback.xml:

Een andere manier om het logboek op DEBUG-niveau in te schakelen, is door het volgende toe te voegen in application.properties:

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

5.2. Configureer traditionele webapplicatie

In de standaard Spring-webapplicatie, Filter kan worden ingesteld via XML-configuratie of Java-configuratie. Laten we het CommonsRequestLoggingFilter met behulp van conventionele op Java gebaseerde configuratie.

Zoals we weten, is de includePayload kenmerk van CommonsRequestLoggingFilter is standaard ingesteld op false. We hebben een aangepaste klasse nodig om de waarde van het attribuut te overschrijven om in te schakelen includePayload voordat u met Java-configuratie in de container injecteert:

openbare klasse CustomeRequestLoggingFilter breidt CommonsRequestLoggingFilter uit {openbare CustomeRequestLoggingFilter () {super.setIncludeQueryString (true); super.setIncludePayload (true); super.setMaxPayloadLength (10000); }}

Nu moeten we de CustomeRequestLoggingFilter met behulp van op Java gebaseerde webinitializer:

openbare klasse CustomWebAppInitializer implementeert WebApplicationInitializer {openbare leegte onStartup (ServletContext-container) {AnnotationConfigWebApplicationContext context = nieuwe AnnotationConfigWebApplicationContext (); context.setConfigLocation ("com.baeldung"); container.addListener (nieuwe ContextLoaderListener (context)); ServletRegistration.Dynamic dispatcher = container.addServlet ("dispatcher", nieuwe DispatcherServlet (context)); dispatcher.setLoadOnStartup (1); dispatcher.addMapping ("/"); container.addFilter ("customRequestLoggingFilter", CustomeRequestLoggingFilter.class) .addMappingForServletNames (null, false, "dispatcher"); }}

6. Voorbeeld in actie

Nu kunnen we een Spring Boot bedraden met context en in actie zien dat het loggen van inkomende verzoeken werkt zoals verwacht:

@Test openbare ongeldig gegevenRequest_whenFetchTaxiFareRateCard_thanOK () {TestRestTemplate testRestTemplate = nieuwe TestRestTemplate (); TaxiRide taxiRide = nieuwe TaxiRide (true, 10l); String tarief = testRestTemplate.postForObject (URL + "berekenen /", taxiRide, String.class); assertThat (fare, equalTo ("200")); }

7. Conclusie

In dit artikel hebben we laten zien hoe basisregistratie van webverzoeken kan worden geïmplementeerd met behulp van interceptors; we hebben ook de beperkingen en uitdagingen van deze oplossing laten zien.

Vervolgens hebben we de ingebouwde filterklasse laten zien die een gebruiksklaar en eenvoudig logboekmechanisme biedt.

Zoals altijd zijn de implementatie van het voorbeeld en de codefragmenten beschikbaar op GitHub.