Spring - Eingehende Anfragen protokollieren

Spring - Eingehende Anfragen protokollieren

1. Einführung

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, überprüfen Sie dieselogging intro article sowie dieSLF4J article.

2. Maven-Abhängigkeiten

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


    org.springframework
    spring-core
    5.0.5.RELEASE

Die neueste Version finden Sie hier fürspring-core.

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 istHandlerInterceptor eine der bemerkenswerten Schnittstellen, mit denen 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 derHandlerInterceptor-Schnittstelle in Form derHandlerInterceptorAdaptor-Klasse, die vom Benutzer erweitert werden kann.

Erstellen wir unseren eigenen Interceptor - indem wirHandlerInterceptorAdaptor as 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 dieTaxiRideRequestInterceptor innerhalb des MVC-Lebenszyklus, um die Vor- und Nachbearbeitung von Controller-Methodenaufrufen zu erfassen, die dem inTaxiFareController 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/**/");
    }
}

Zusammenfassend addiertWebMvcConfigurer denTaxiFareRequestInterceptor innerhalb des MVC-Lebenszyklus der Feder, indem die MethodeaddInterceptors()aufgerufen wird.

Die größte Herausforderung besteht darin, die Kopien der Anforderungs- und Antwortnutzdaten für die Protokollierung abzurufen und die angeforderte Nutzdaten für das Servlet zur Verarbeitung zu belassen.

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

To overcome this problem können wir das Caching nutzen, um den Anforderungsdatenstrom zu speichern und für die Protokollierung zu verwenden.

Spring bietet nur wenige nützliche Klassen wieContentCachingRequestWrapper undContentCachingResponseWrapper, die zum Zwischenspeichern der Anforderungsdaten für Protokollierungszwecke verwendet werden können.

Passen wir unserepreHandle() derTaxiRideRequestInterceptor-Klasse an, um das Anforderungsobjekt mit derContentCachingRequestWrapper-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 mit der KlasseContentCachingRequestWrapperzwischengespeichert, mit der die Nutzdaten für die Protokollierung gelesen werden können, ohne das eigentliche Anforderungsobjekt zu stören:

requestCacheWrapperObject.getContentAsByteArray();

Verjährung

  • Die Klasse vonContentCachingRequestWrapperunterstützt nur Folgendes:

Content-Type:application/x-www-form-urlencoded
Method-Type:POST
  • Wir müssen die folgende Methode aufrufen, um sicherzustellen, dass Anforderungsdaten inContentCachingRequestWrapper zwischengespeichert werden, bevor wir sie verwenden:

requestCacheWrapperObject.getParameterMap();

5. Integrierte Anforderungsprotokollierung im Frühjahr

Spring bietet eine integrierte Lösung für die Protokollierung von Nutzdaten. Wir können vorgefertigte Filter verwenden, indem wir sie mithilfe der Konfiguration in die Spring-Anwendung einstecken.

AbstractRequestLoggingFilter ist ein Filter, der grundlegende Funktionen der Protokollierung bereitstellt. Unterklassen sollten die MethodenbeforeRequest() undafterRequest() überschreiben, um die eigentliche Protokollierung der Anforderung durchzuführen.

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 mitCommonsRequestLoggingFilterfort und konfigurieren Sie es 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 festgelegt sein. Wir können den DEBUG-Modus aktivieren, indem wir das folgende Element inlogback.xml hinzufügen:


    

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

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

5.2. Konfigurieren Sie die herkömmliche Webanwendung

In der Standard-Spring-Webanwendung könnenFilter entweder über die XML-Konfiguration oder die Java-Konfiguration festgelegt werden. Lassen Sie uns dieCommonsRequestLoggingFilter mit einer herkömmlichen Java-basierten Konfiguration einrichten.

Wie wir wissen, ist dasincludePayload-Attribut vonCommonsRequestLoggingFilter standardmäßig auf false gesetzt. Wir benötigen eine benutzerdefinierte Klasse, um den Wert des Attributs zu überschreiben undincludePayload zu aktivieren, bevor Sie mithilfe der Java-Konfiguration in den Container injizieren:

public class CustomeRequestLoggingFilter
  extends CommonsRequestLoggingFilter {

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

Jetzt müssen wir dieCustomeRequestLoggingFilter mitJava based web initializer injizieren:

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. Beispiel in Aktion

Jetzt können wir einen Spring Boot mit dem Kontext verbinden und in Aktion feststellen, dass die Protokollierung eingehender Anforderungen wie erwartet funktioniert:

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

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

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

Wie immer sind die Implementierung der Beispiel- und Codefragmenteover on GitHub. verfügbar