Spring - Eingehende Anfragen protokollieren

1. Einleitung

In diesem kurzen Tutorial zeigen wir die Grundlagen der Protokollierung eingehender Anforderungen mithilfe des Protokollierungsfilters von Spring. Wenn Sie gerade erst mit der Protokollierung beginnen, lesen Sie diesen Artikel zur Protokollierung sowie den SLF4J-Artikel.

2. Maven-Abhängigkeiten

Die Protokollierungsabhängigkeiten sind einfach dieselben wie im Intro-Artikel. Fügen wir hier einfach Spring hinzu:

 org.springframework spring-core 5.2.2.RELEASE 

Die neueste Version für Spring-Core finden Sie hier.

3. Grundlegender Web Controller

Definieren wir zunächst einen Controller, der in unserem Beispiel verwendet wird:

@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. Benutzerdefinierte Anforderungsprotokollierung

Spring bietet einen Mechanismus zum Konfigurieren benutzerdefinierter Interceptors, um Aktionen vor und nach Webanforderungen auszuführen.

Unter den Spring Request Interceptors ist eine der bemerkenswerten Schnittstellen HandlerInterceptor , mit der die eingehende Anfrage durch Implementierung der folgenden Methoden protokolliert werden kann:

  1. preHandle () - Diese Methode wird vor der eigentlichen Controller-Servicemethode ausgeführt
  2. afterCompletion () - Diese Methode wird ausgeführt, nachdem der Controller bereit ist, die Antwort zu senden

Darüber hinaus bietet Spring die Standardimplementierung der HandlerInterceptor- Schnittstelle in Form der HandlerInterceptorAdaptor- Klasse, die vom Benutzer erweitert werden kann.

Erstellen wir unseren eigenen Interceptor, indem wir HandlerInterceptorAdaptor wie folgt erweitern :

@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) { // } }

Schließlich konfigurieren wir den TaxiRideRequestInterceptor innerhalb des MVC-Lebenszyklus, um die Vor- und Nachbearbeitung von Controller-Methodenaufrufen zu erfassen, die dem in der TaxiFareController- Klasse definierten Pfad / Taxifare zugeordnet sind .

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

Im Ergebnis der WebMvcConfigurer fügt den TaxiFareRequestInterceptor innerhalb Feder MVC - Lebenszyklus durch Aufrufe addInterceptors () Methode.

Die größte Herausforderung besteht darin, die Kopien der Anforderungs- und Antwortnutzdaten für die Protokollierung abzurufen und die angeforderten Nutzdaten dennoch dem Servlet zur Verarbeitung zu überlassen.

Das Hauptproblem bei der Leseanforderung besteht darin, dass der Eingabestream beim ersten Lesen als verbraucht markiert wird und nicht mehr gelesen werden kann.

Die Anwendung löst nach dem Lesen des Anforderungsstroms eine Ausnahme aus:

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

Um dieses Problem zu lösen , können wir das Caching nutzen, um den Anforderungsdatenstrom zu speichern und für die Protokollierung zu verwenden.

Spring bietet einige nützliche Klassen wie ContentCachingRequestWrapper und ContentCachingResponseWrapper, die zum Zwischenspeichern der Anforderungsdaten für Protokollierungszwecke verwendet werden können.

Passen wir unser preHandle () der TaxiRideRequestInterceptor- Klasse an, um das Anforderungsobjekt mithilfe der ContentCachingRequestWrapper- Klasse zwischenzuspeichern.

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

Wie wir sehen können, haben wir das Anforderungsobjekt mithilfe der ContentCachingRequestWrapper- Klasse zwischengespeichert, mit der die Nutzdaten für die Protokollierung gelesen werden können, ohne das eigentliche Anforderungsobjekt zu stören:

requestCacheWrapperObject.getContentAsByteArray();

Einschränkung

  • Die ContentCachingRequestWrapper- Klasse unterstützt nur Folgendes:
Content-Type:application/x-www-form-urlencoded Method-Type:POST
  • Wir müssen die folgende Methode aufrufen, um sicherzustellen, dass Anforderungsdaten in ContentCachingRequestWrapper zwischengespeichert werden, bevor wir sie verwenden:
requestCacheWrapperObject.getParameterMap();

5. Integrierte Anforderungsprotokollierung im Frühjahr

Spring bietet eine integrierte Lösung zum Protokollieren von Nutzdaten. Wir können vorgefertigte Filter verwenden, indem wir die Konfiguration mithilfe der Konfiguration an die Spring-Anwendung anschließen.

AbstractRequestLoggingFilter ist ein Filter, der grundlegende Funktionen der Protokollierung bereitstellt. Unterklassen sollten die Methoden beforeRequest () und afterRequest () überschreiben , um die eigentliche Protokollierung der Anforderung durchzuführen.

Das Spring Framework bietet drei konkrete Implementierungsklassen, mit denen die eingehende Anforderung protokolliert werden kann. Diese drei Klassen sind:

  • CommonsRequestLoggingFilter
  • Log4jNestedDiagnosticContextFilter (veraltet)
  • ServletContextRequestLoggingFilter

Fahren wir nun mit dem CommonsRequestLoggingFilter fort und konfigurieren ihn so, dass eingehende Anforderungen für die Protokollierung erfasst werden.

5.1. Konfigurieren Sie die Spring Boot-Anwendung

Die Spring Boot-Anwendung kann durch Hinzufügen einer Bean-Definition konfiguriert werden, um die Anforderungsprotokollierung zu aktivieren:

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

Für diesen Protokollierungsfilter muss außerdem die Protokollstufe auf DEBUG eingestellt sein. Wir können den DEBUG-Modus aktivieren, indem wir das folgende Element in logback.xml hinzufügen :

Eine andere Möglichkeit, das DEBUG-Level-Protokoll zu aktivieren, besteht darin, in application.properties Folgendes hinzuzufügen :

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

5.2. Konfigurieren Sie die herkömmliche Webanwendung

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

In diesem Artikel haben wir gezeigt, wie die grundlegende Webanforderungsprotokollierung mithilfe von Interceptors implementiert wird. Wir haben auch die Grenzen und Herausforderungen dieser Lösung aufgezeigt.

Dann haben wir die integrierte Filterklasse gezeigt, die einen gebrauchsfertigen und einfachen Protokollierungsmechanismus bietet.

Wie immer ist die Implementierung der Beispiel- und Codefragmente auf GitHub verfügbar.