28 Stimmen

Protokollierung des Antwortkörpers (HTML) von HttpServletResponse mit Spring MVC HandlerInterceptorAdapter

Ich versuche zu protokollieren (nur auf die Konsole schreiben jetzt der Einfachheit halber) die endgültige gerenderte HTML, die von der HttpServletResponse zurückgegeben werden wird. (d. h. der Körper) Zu diesem Zweck verwende ich den HandlerInterceptorAdapter von Spring MVC wie folgt:

public class VxmlResponseInterceptor extends HandlerInterceptorAdapter {
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println(response.toString());
    }
}

Dies funktioniert wie erwartet, und ich sehe die HTTP-Antwort-Header in der Konsole. Meine Frage ist, ob es eine relativ einfache Möglichkeit gibt, die gesamte Antwort Körper (d.h. endgültige gerenderte HTML) auf der Konsole zu protokollieren, ohne Hampelmänner mit PrintWriters, OutputStreams und dergleichen zu tun.

Vielen Dank im Voraus.

0 Stimmen

Dies geschieht in der Regel mit Hilfe des Containers...., in dem Sie das Programm ausführen.

0 Stimmen

Ich führe es innerhalb von Jetty 7 über das jetty-maven-plugin aus, aber ich verstehe nicht, warum das eine Rolle spielen sollte. Ich möchte die HTML-Antwort sehen, die der Browser erhalten wird.

25voto

skaffman Punkte 389758

Dies sollte besser mit einem Servlet geschehen Filter als eine Feder HandlerInterceptor aus dem Grund, dass ein Filter ist es erlaubt, die Anfrage- und/oder Antwortobjekte zu ersetzen, und Sie könnten diesen Mechanismus nutzen, um die Antwort durch einen Wrapper zu ersetzen, der die Antwortausgabe protokolliert.

Dies würde das Schreiben einer Unterklasse von HttpServletResponseWrapper überschreiben. getOutputStream (und möglicherweise auch getWriter() ). Diese Methoden würden Folgendes zurückgeben OutputStream / PrintWriter Implementierungen, die den Antwortstrom nicht nur an das ursprüngliche Ziel, sondern auch in ein Protokoll ableiten. Eine einfache Möglichkeit, dies zu tun, ist die Verwendung von TeeOutputStream von Apache Commons IO aber es ist nicht schwer, sie selbst zu implementieren.

Hier ein Beispiel dafür, wie Sie mit der Spring-Funktion GenericFilterBean y DelegatingServletResponseStream sowie TeeOutputStream um die Dinge zu erleichtern:

public class ResponseLoggingFilter extends GenericFilterBean {

   @Override
   public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException {
      HttpServletResponse responseWrapper = loggingResponseWrapper((HttpServletResponse) response);     
      filterChain.doFilter(request, responseWrapper);
   }

   private HttpServletResponse loggingResponseWrapper(HttpServletResponse response) {
      return new HttpServletResponseWrapper(response) {
         @Override
         public ServletOutputStream getOutputStream() throws IOException {
            return new DelegatingServletOutputStream(
               new TeeOutputStream(super.getOutputStream(), loggingOutputStream())
            );
         }
      };
   }

   private OutputStream loggingOutputStream() {
      return System.out;
   }
}

Dies protokolliert alles auf STDOUT. Wenn Sie in eine Datei protokollieren wollen, wird es etwas komplizierter, da Sie sicherstellen müssen, dass die Streams geschlossen werden usw., aber das Prinzip bleibt das gleiche.

0 Stimmen

In der Tat wird es komplizierter, da close nicht auf dem DelegatingServletOutputStream aufgerufen wird, da der Wrapper zu dem Zeitpunkt, an dem der Container den Stream schließt, außerhalb des Anwendungsbereichs liegt. Irgendwelche Ideen, wie Sie über dieses gehen könnte?

9voto

Zoran Regvart Punkte 4558

Wenn Sie Folgendes verwenden (oder in Erwägung ziehen) logback als Ihr Logging-Framework verwenden, gibt es bereits einen schönen Servlet-Filter, der genau das tut. Sehen Sie sich das Kapitel TeeFilter in der Dokumentation .

6voto

Mr Twiggs Punkte 121

Ich habe eine Weile nach einer Möglichkeit gesucht, vollständige HTTP-Anfragen und -Antworten zu protokollieren, und habe entdeckt, dass das Problem für mich in der Tomcat 7 RequestDumperFilter . Es funktioniert wie angekündigt in einem Tomcat 7 Container. Wenn Sie sie in Jetty verwenden möchten, funktioniert die Klasse gut als eigenständige Lösung oder, wie ich es getan habe, kopiert und an die spezifischen Bedürfnisse meiner Umgebung angepasst.

0 Stimmen

Sieht so aus, als ob dieser Filter auch in dieser Antwort behandelt wird: stackoverflow.com/questions/5672904/ .

1 Stimmen

Herr Twiggs, Tomcat 7 RequestDumperFilter kann nur Header dumpen, was viel einfacher ist.

2voto

Israel Zalmanov Punkte 751

Ich habe eine kleine Bibliothek spring-mvc-logger verfügbar über Maven Central.

Zur pom.xml hinzufügen:

<dependency>
    <groupId>com.github.isrsal</groupId>
    <artifactId>spring-mvc-logger</artifactId>
    <version>0.2</version>
</dependency>

Zu web.xml hinzufügen:

<filter>
    <filter-name>loggingFilter</filter-name>
    <filter-class>com.github.isrsal.logging.LoggingFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>loggingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

Zu log4j.xml hinzufügen:

<logger name="com.github.isrsal.logging.LoggingFilter">
    <level value="DEBUG"/>
</logger>

0 Stimmen

Nach so vielen Jahren funktioniert es immer noch. Vielen Dank, es rettet mir den Tag.

1voto

Andrea Saba Punkte 171

Der unten eingefügte Code funktioniert mit meinen Tests und kann von meiner Website heruntergeladen werden Github-Projekt nach Anwendung einer darauf basierenden Lösung auf ein Produktionsprojekt

    @Configuration
public class LoggingFilter extends GenericFilterBean {

    /**
     * It's important that you actually register your filter this way rather then just annotating it
     * as @Component as you need to be able to set for which "DispatcherType"s to enable the filter
     * (see point *1*)
     * 
     * @return
     */
    @Bean
    public FilterRegistrationBean<LoggingFilter> initFilter() {
        FilterRegistrationBean<LoggingFilter> registrationBean = new FilterRegistrationBean<>();
        registrationBean.setFilter(new LoggingFilter());

        // *1* make sure you sett all dispatcher types if you want the filter to log upon
        registrationBean.setDispatcherTypes(EnumSet.allOf(DispatcherType.class));

        // *2* this should put your filter above any other filter
        registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);

        return registrationBean;
    }

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {

        ContentCachingRequestWrapper wreq = 
            new ContentCachingRequestWrapper(
                (HttpServletRequest) request);

        ContentCachingResponseWrapper wres = 
            new ContentCachingResponseWrapper(
                (HttpServletResponse) response);

        try {

            // let it be ...
            chain.doFilter(wreq, wres);

            // makes sure that the input is read (e.g. in 404 it may not be)
            while (wreq.getInputStream().read() >= 0);

            System.out.printf("=== REQUEST%n%s%n=== end request%n",
                    new String(wreq.getContentAsByteArray()));

            // Do whatever logging you wish here, in this case I'm writing request 
            // and response to system out which is probably not what you wish to do
            System.out.printf("=== RESPONSE%n%s%n=== end response%n",
                    new String(wres.getContentAsByteArray()));

            // this is specific of the "ContentCachingResponseWrapper" we are relying on, 
            // make sure you call it after you read the content from the response
            wres.copyBodyToResponse();

            // One more point, in case of redirect this will be called twice! beware to handle that
            // somewhat

        } catch (Throwable t) {
            // Do whatever logging you whish here, too
            // here you should also be logging the error!!!
            throw t;
        }

    }
}

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X