Spring Boot - Wie protokolliere ich alle Anfragen und Antworten mit Ausnahmen an einem Ort?


215

Ich arbeite an Rest API mit Spring Boot. Ich muss alle Anforderungen mit Eingabeparametern (mit Methoden, z. B. GET, POST usw.), Anforderungspfad, Abfragezeichenfolge, entsprechender Klassenmethode dieser Anforderung sowie Antwort auf diese Aktion, sowohl Erfolg als auch Fehler, protokollieren.

Zum Beispiel:

erfolgreiche Anfrage:

http://example.com/api/users/1

Das Protokoll sollte ungefähr so ​​aussehen:

{
   HttpStatus: 200,
   path: "api/users/1",
   method: "GET",
   clientIp: "0.0.0.0",
   accessToken: "XHGu6as5dajshdgau6i6asdjhgjhg",
   method: "UsersController.getUser",
   arguments: {
     id: 1 
   },
   response: {
      user: {
        id: 1,
        username: "user123",
        email: "user123@example.com"   
      }
   },
   exceptions: []       
}

Oder Anfrage mit Fehler:

http://example.com/api/users/9999

Das Protokoll sollte ungefähr so ​​aussehen:

    {
       HttpStatus: 404,
       errorCode: 101,                 
       path: "api/users/9999",
       method: "GET",
       clientIp: "0.0.0.0",
       accessToken: "XHGu6as5dajshdgau6i6asdjhgjhg",
       method: "UsersController.getUser",
       arguments: {
         id: 9999 
       },
       returns: {            
       },
       exceptions: [
         {
           exception: "UserNotFoundException",
           message: "User with id 9999 not found",
           exceptionId: "adhaskldjaso98d7324kjh989",
           stacktrace: ...................    
       ]       
    }

Ich möchte, dass Request / Response eine einzelne Entität mit benutzerdefinierten Informationen zu dieser Entität ist, sowohl in erfolgreichen als auch in Fehlerfällen.

Was ist die beste Vorgehensweise im Frühjahr, um dies zu erreichen, möglicherweise mit Filtern? Wenn ja, können Sie ein konkretes Beispiel geben?

(Ich habe mit @ControllerAdvice und @ExceptionHandler gespielt, aber wie bereits erwähnt, muss ich alle Erfolgs- und Fehleranforderungen an einem einzigen Ort (und in einem einzigen Protokoll) bearbeiten.)


Wahrscheinlich über eine Protokollierung ServletFilter (zB stackoverflow.com/a/2171633/995891 ), alternativ HandlerInterceptoraber das ist nicht gut funktionieren kann mit der Anmeldung der Antwort , wie in der Antwort erwähnt: concretepage.com/spring/spring-mvc/... - HandlerInterceptor hat Zugang auf die Methode (Methode: "UsersController.getUser"). Das ist bei einem Servlet-Filter nicht bekannt.
Zapl

1
Selbst wenn Sie einen Filter oder eine andere Lösung auf Anwendungsebene hinzufügen, werden nicht alle Anforderungen protokolliert, z. B. wird der HTTP 500-Serverfehler nicht protokolliert, da zu dem Zeitpunkt eine nicht behandelte Ausnahme auf der Anwendungsebene ausgelöst wird. Die Standard-Embedded-Tomcat-Fehlerseite wird nach dem Verschlucken der Ausnahme angezeigt, und natürlich wird das Protokoll nicht beibehalten. Auch wenn Sie die Antwort von user1817243 überprüfen, protokolliert er im Falle einer Ausnahme die Anfrage erneut nicht, aber er protokolliert die Ausnahme (!!).
AntJavaDev

Muss dieses Protokollformat mit jedem von Ihnen geschriebenen Zeichen übereinstimmen? Scheint, als wäre eine JSON-Übersetzung in Ihrem Fall optimal: LogClass{ getRequestAndSaveIt()} Gson.toJson(LogClass)als Pseudocode
Vale

1
Zukünftige Leser können von meiner Antwort profitieren (URL folgt in diesem Kommentar). Grundsätzlich konnte ich verschiedene Beiträge zu dieser Frage zusammenstellen. BITTE berücksichtigen Sie die Antwort des Stellantriebs (in den Antworten unten), bevor Sie sie von Hand versuchen. Die Antwort, die ich veröffentliche, ermöglicht jedoch die Protokollierung von "400, 404, 500" (beliebig / alle), setzt jedoch die Auftragspriorität auf die niedrigste Priorität (oder innerhalb von "8", wenn Sie sich den Code ansehen). stackoverflow.com/questions/10210645/…
granadaCoder

Ich habe die Frühlingsdokumente zum Protokollieren von hier aus befolgt
T04435

Antworten:


146

Schreiben Sie keine Interceptors, Filter, Komponenten, Aspekte usw., dies ist ein sehr häufiges Problem und wurde mehrfach gelöst.

Spring Boot verfügt über ein Modul namens Actuator , das die sofortige Protokollierung von HTTP-Anforderungen ermöglicht. Es gibt einen Endpunkt, der /trace(SB1.x) oder /actuator/httptrace(SB2.0 +) zugeordnet ist und der Ihnen die letzten 100 HTTP-Anforderungen anzeigt. Sie können es anpassen, um jede Anforderung zu protokollieren oder in eine Datenbank zu schreiben.

Um die gewünschten Endpunkte zu erhalten, benötigen Sie die Spring-Boot-Starter-Actuator-Abhängigkeit sowie die "Whitelist" der gesuchten Endpunkte und möglicherweise das Einrichten oder Deaktivieren der Sicherheit dafür.

Wo wird diese Anwendung ausgeführt? Verwenden Sie ein PaaS? Hosting-Anbieter, beispielsweise Heroku, bieten die Anforderungsprotokollierung als Teil ihres Dienstes an, und Sie müssen dann überhaupt keine Codierung vornehmen.


4
Noch mehr Details? Ich habe github.com/spring-projects/spring-boot/tree/master/… gefunden , aber nicht viel darüber hinaus.
Tom Howard

16
Dies kann nicht zum Debuggen verwendet werden: Nicht authentifizierte Anforderungen (z. B. mit Federsicherheit) werden nicht protokolliert.
bekce

11
Tatsächlich verfügt Actuator über keine spezifischen Komponenten, um die http-Protokollierung zu aktivieren. / trace - zeigt nur die letzten N Anfragen an.
Vladimir Filipchenko

18
@ike_love, wie konfiguriere ich den Aktuator so, dass er die Protokollierungsanforderung (auch den POST-Body) in die Datei protokolliert?

11
Trace protokolliert nicht den Anforderungs- und Antworttext für Sie .... alles andere (Header usw.) außer diesen.
Lekkie

94

Spring bietet bereits einen Filter, der diese Aufgabe übernimmt. Fügen Sie Ihrer Konfiguration die folgende Bean hinzu

@Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
    CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();
    loggingFilter.setIncludeClientInfo(true);
    loggingFilter.setIncludeQueryString(true);
    loggingFilter.setIncludePayload(true);
    loggingFilter.setMaxPayloadLength(64000);
    return loggingFilter;
}

Vergessen Sie nicht, die Protokollstufe von org.springframework.web.filter.CommonsRequestLoggingFilterauf zu ändern DEBUG.


74
Beachten Sie, dass nicht die Antworten, sondern nur die Anforderungen protokolliert werden.
Wim Deblauwe

1
Es gibt nur Anfragen. Wie protokolliere ich Antwortkörper mit CommonsRequestLoggingFilter?
user2602807

3
Auch dies protokolliert keine Ausnahme
BhendiGawaar

Nun, das wird erwartet, da es sich um einen Anforderungsprotokollierungsfilter handelt. Mehr dazu hier: docs.spring.io/spring/docs/current/javadoc-api/org/…
Yogesh Badke

4
Wenn Sie einen großen JSON-Body haben, setzen Sie die Nutzlastlänge auf eine große Zahl, um den gesamten Anfragetext zu protokollieren. loggingFilter.setMaxPayloadLength (100000);
Venkatesh Nannan

57

Sie können verwenden, javax.servlet.Filterwenn die ausgeführte Java-Methode nicht protokolliert werden muss.

Aber mit dieser Anforderung haben Sie den Zugriff auf gespeicherte Informationen in handlerMappingder DispatcherServlet. Sie können jedoch überschreiben DispatcherServlet, um die Protokollierung des Anforderungs- / Antwortpaars durchzuführen.

Im Folgenden finden Sie ein Beispiel für eine Idee, die weiter verbessert und an Ihre Bedürfnisse angepasst werden kann.

public class LoggableDispatcherServlet extends DispatcherServlet {

    private final Log logger = LogFactory.getLog(getClass());

    @Override
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        if (!(request instanceof ContentCachingRequestWrapper)) {
            request = new ContentCachingRequestWrapper(request);
        }
        if (!(response instanceof ContentCachingResponseWrapper)) {
            response = new ContentCachingResponseWrapper(response);
        }
        HandlerExecutionChain handler = getHandler(request);

        try {
            super.doDispatch(request, response);
        } finally {
            log(request, response, handler);
            updateResponse(response);
        }
    }

    private void log(HttpServletRequest requestToCache, HttpServletResponse responseToCache, HandlerExecutionChain handler) {
        LogMessage log = new LogMessage();
        log.setHttpStatus(responseToCache.getStatus());
        log.setHttpMethod(requestToCache.getMethod());
        log.setPath(requestToCache.getRequestURI());
        log.setClientIp(requestToCache.getRemoteAddr());
        log.setJavaMethod(handler.toString());
        log.setResponse(getResponsePayload(responseToCache));
        logger.info(log);
    }

    private String getResponsePayload(HttpServletResponse response) {
        ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        if (wrapper != null) {

            byte[] buf = wrapper.getContentAsByteArray();
            if (buf.length > 0) {
                int length = Math.min(buf.length, 5120);
                try {
                    return new String(buf, 0, length, wrapper.getCharacterEncoding());
                }
                catch (UnsupportedEncodingException ex) {
                    // NOOP
                }
            }
        }
        return "[unknown]";
    }

    private void updateResponse(HttpServletResponse response) throws IOException {
        ContentCachingResponseWrapper responseWrapper =
            WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
        responseWrapper.copyBodyToResponse();
    }

}

HandlerExecutionChain - enthält die Informationen zum Request Handler.

Sie können diesen Dispatcher dann wie folgt registrieren:

    @Bean
    public ServletRegistrationBean dispatcherRegistration() {
        return new ServletRegistrationBean(dispatcherServlet());
    }

    @Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
    public DispatcherServlet dispatcherServlet() {
        return new LoggableDispatcherServlet();
    }

Und hier ist das Beispiel von Protokollen:

http http://localhost:8090/settings/test
i.g.m.s.s.LoggableDispatcherServlet      : LogMessage{httpStatus=500, path='/error', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)] and 3 interceptors', arguments=null, response='{"timestamp":1472475814077,"status":500,"error":"Internal Server Error","exception":"java.lang.RuntimeException","message":"org.springframework.web.util.NestedServletException: Request processing failed; nested exception is java.lang.RuntimeException","path":"/settings/test"}'}

http http://localhost:8090/settings/params
i.g.m.s.s.LoggableDispatcherServlet      : LogMessage{httpStatus=200, path='/settings/httpParams', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public x.y.z.DTO x.y.z.Controller.params()] and 3 interceptors', arguments=null, response='{}'}

http http://localhost:8090/123
i.g.m.s.s.LoggableDispatcherServlet      : LogMessage{httpStatus=404, path='/error', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)] and 3 interceptors', arguments=null, response='{"timestamp":1472475840592,"status":404,"error":"Not Found","message":"Not Found","path":"/123"}'}

AKTUALISIEREN

Im Fehlerfall führt Spring eine automatische Fehlerbehandlung durch. Daher BasicErrorController#errorwird als Anforderungshandler angezeigt. Wenn Sie den ursprünglichen Anforderungshandler beibehalten möchten, können Sie dieses Verhalten überschreiben, spring-webmvc-4.2.5.RELEASE-sources.jar!/org/springframework/web/servlet/DispatcherServlet.java:971bevor #processDispatchResultes aufgerufen wird, um den ursprünglichen Handler zwischenzuspeichern.


2
Was passiert, wenn die Antwort ein Stream ist und der Stream die Suche nicht unterstützt? Funktioniert das noch?
Tom Howard

Die aufgerufene Methode ist mir egal, nur die empfangenen und gesendeten Daten. Der Filter scheint mich in die richtige Richtung zu weisen und die Antwort von @ ike_love hat mich an github.com/spring-projects/spring-boot/blob/master/…
Tom Howard

@ TomHoward AFAIK, im Frühjahr gibt es keine sofort einsatzbereite "Antwortprotokollierung". Daher können Sie WebRequestTraceFilter oder AbstractRequestLoggingFilter um eine Antwortprotokollierungslogik erweitern.
Hahn

Funktioniert gut!
Pavel Vlasov

@hahn warum hast du dafür das Dispatcher Servlet benutzt? Kann das gleiche Login nicht mit dem Filter in doFilter hinzugefügt werden?
BhendiGawaar

39

Die Logbuchbibliothek wurde speziell zum Protokollieren von HTTP-Anforderungen und -Antworten erstellt. Es unterstützt Spring Boot mithilfe einer speziellen Starterbibliothek.

Um die Anmeldung bei Spring Boot zu aktivieren, müssen Sie lediglich die Bibliothek zu den Abhängigkeiten Ihres Projekts hinzufügen. Angenommen, Sie verwenden Maven:

<dependency>
    <groupId>org.zalando</groupId>
    <artifactId>logbook-spring-boot-starter</artifactId>
    <version>1.5.0</version>
</dependency>

Standardmäßig sieht die Protokollausgabe folgendermaßen aus:

{
  "origin" : "local",
  "correlation" : "52e19498-890c-4f75-a06c-06ddcf20836e",
  "status" : 200,
  "headers" : {
    "X-Application-Context" : [
      "application:8088"
    ],
    "Content-Type" : [
      "application/json;charset=UTF-8"
    ],
    "Transfer-Encoding" : [
      "chunked"
    ],
    "Date" : [
      "Sun, 24 Dec 2017 13:10:45 GMT"
    ]
  },
  "body" : {
    "thekey" : "some_example"
  },
  "duration" : 105,
  "protocol" : "HTTP/1.1",
  "type" : "response"
}

Es wird jedoch nicht der Klassenname ausgegeben, der die Anforderung verarbeitet. Die Bibliothek verfügt über einige Schnittstellen zum Schreiben von benutzerdefinierten Loggern.


4
Als Abhängigkeit zu einer minimalen Spring-Boot-App hinzugefügt und versucht, ausgeführt zu werden - keine Änderung, überhaupt keine Protokollausgabe in meiner App. Ich denke, es gibt einige zusätzliche Abhängigkeiten oder Klassen, die dies benötigt? Das Registrieren als Filter scheint auch nichts zu bewirken.
uvb

1
@eis Sie müssen es als Filter registrieren, wie in den Dokumenten hier erläutert. github.com/zalando/logbook
Pratik Singhal

1
Das Logbuch-Dokument sagt: "Das Logbuch verfügt über eine praktische automatische Konfiguration für Spring Boot-Benutzer. Es richtet alle folgenden Teile automatisch mit sinnvollen Standardeinstellungen ein." Aber es funktioniert nicht.
Leos Literak

5
@LeosLiterak Ich glaube, Sie müssen zu logging.level.org.zalando.logbook=TRACE Ihrem application.properties(wie in der Readme)
TolkienWASP

2
Die automatische
Konfiguration des

26

Ich hatte die Anmeldeebene application.propertiesfür das Drucken von Anforderungen / Antworten und die Methoden-URL in der Protokolldatei definiert

logging.level.org.springframework.web=DEBUG
logging.level.org.hibernate.SQL=INFO
logging.file=D:/log/myapp.log

Ich hatte Spring Boot benutzt.


2
Ja, Sie haben Recht - dies ist eine gültige Antwort für das Abrufen von Anforderungen, die mit allen anderen Ergebnissen in derselben Protokolldatei protokolliert werden. Allerdings bat @moreo, GET, POST usw. und in der separaten Datei (wie ich verstehe) zu
protokollieren

4
Ich mag diesen. Null Drama
Quirino Gervacio

1
Wenn Sie möchten, dass die Header in das Protokoll aufgenommen werden, sollten Sie Ihrer Datei application.properties Folgendes hinzufügen: "spring.http.log-request-details = true".
jfajunior

20

Hier ist, wie ich es im Frühjahr mache Daten ruhen mit org.springframework.web.util.ContentCachingRequestWrapper und org.springframework.web.util.ContentCachingResponseWrapper

/**
 * Doogies very cool HTTP request logging
 *
 * There is also {@link org.springframework.web.filter.CommonsRequestLoggingFilter}  but it cannot log request method
 * And it cannot easily be extended.
 *
 * https://mdeinum.wordpress.com/2015/07/01/spring-framework-hidden-gems/
 * http://stackoverflow.com/questions/8933054/how-to-read-and-copy-the-http-servlet-response-output-stream-content-for-logging
 */
public class DoogiesRequestLogger extends OncePerRequestFilter {

  private boolean includeResponsePayload = true;
  private int maxPayloadLength = 1000;

  private String getContentAsString(byte[] buf, int maxLength, String charsetName) {
    if (buf == null || buf.length == 0) return "";
    int length = Math.min(buf.length, this.maxPayloadLength);
    try {
      return new String(buf, 0, length, charsetName);
    } catch (UnsupportedEncodingException ex) {
      return "Unsupported Encoding";
    }
  }

  /**
   * Log each request and respponse with full Request URI, content payload and duration of the request in ms.
   * @param request the request
   * @param response the response
   * @param filterChain chain of filters
   * @throws ServletException
   * @throws IOException
   */
  @Override
  protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {

    long startTime = System.currentTimeMillis();
    StringBuffer reqInfo = new StringBuffer()
     .append("[")
     .append(startTime % 10000)  // request ID
     .append("] ")
     .append(request.getMethod())
     .append(" ")
     .append(request.getRequestURL());

    String queryString = request.getQueryString();
    if (queryString != null) {
      reqInfo.append("?").append(queryString);
    }

    if (request.getAuthType() != null) {
      reqInfo.append(", authType=")
        .append(request.getAuthType());
    }
    if (request.getUserPrincipal() != null) {
      reqInfo.append(", principalName=")
        .append(request.getUserPrincipal().getName());
    }

    this.logger.debug("=> " + reqInfo);

    // ========= Log request and response payload ("body") ========
    // We CANNOT simply read the request payload here, because then the InputStream would be consumed and cannot be read again by the actual processing/server.
    //    String reqBody = DoogiesUtil._stream2String(request.getInputStream());   // THIS WOULD NOT WORK!
    // So we need to apply some stronger magic here :-)
    ContentCachingRequestWrapper wrappedRequest = new ContentCachingRequestWrapper(request);
    ContentCachingResponseWrapper wrappedResponse = new ContentCachingResponseWrapper(response);

    filterChain.doFilter(wrappedRequest, wrappedResponse);     // ======== This performs the actual request!
    long duration = System.currentTimeMillis() - startTime;

    // I can only log the request's body AFTER the request has been made and ContentCachingRequestWrapper did its work.
    String requestBody = this.getContentAsString(wrappedRequest.getContentAsByteArray(), this.maxPayloadLength, request.getCharacterEncoding());
    if (requestBody.length() > 0) {
      this.logger.debug("   Request body:\n" +requestBody);
    }

    this.logger.debug("<= " + reqInfo + ": returned status=" + response.getStatus() + " in "+duration + "ms");
    if (includeResponsePayload) {
      byte[] buf = wrappedResponse.getContentAsByteArray();
      this.logger.debug("   Response body:\n"+getContentAsString(buf, this.maxPayloadLength, response.getCharacterEncoding()));
    }

    wrappedResponse.copyBodyToResponse();  // IMPORTANT: copy content of response back into original response

  }


}

18

Wenn es Ihnen nichts ausmacht, Spring AOP auszuprobieren, habe ich dies zu Protokollierungszwecken untersucht und es funktioniert ziemlich gut für mich. Nicht definierte Anforderungen und fehlgeschlagene Anforderungsversuche werden jedoch nicht protokolliert.

Fügen Sie diese drei Abhängigkeiten hinzu

spring-aop, aspectjrt, aspectjweaver

Fügen Sie dies Ihrer XML-Konfigurationsdatei hinzu <aop:aspectj-autoproxy/>

Erstellen Sie eine Anmerkung, die als Pointcut verwendet werden kann

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface EnableLogging {
ActionType actionType();
}

Kommentieren Sie nun alle Ihre Rest-API-Methoden, die Sie protokollieren möchten

@EnableLogging(actionType = ActionType.SOME_EMPLOYEE_ACTION)
@Override
public Response getEmployees(RequestDto req, final String param) {
...
}

Nun zum Aspekt. Komponenten-Scan des Pakets, in dem sich diese Klasse befindet.

@Aspect
@Component
public class Aspects {

@AfterReturning(pointcut = "execution(@co.xyz.aspect.EnableLogging * *(..)) && @annotation(enableLogging) && args(reqArg, reqArg1,..)", returning = "result")
public void auditInfo(JoinPoint joinPoint, Object result, EnableLogging enableLogging, Object reqArg, String reqArg1) {

    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
            .getRequest();

    if (result instanceof Response) {
        Response responseObj = (Response) result;

    String requestUrl = request.getScheme() + "://" + request.getServerName()
                + ":" + request.getServerPort() + request.getContextPath() + request.getRequestURI()
                + "?" + request.getQueryString();

String clientIp = request.getRemoteAddr();
String clientRequest = reqArg.toString();
int httpResponseStatus = responseObj.getStatus();
responseObj.getEntity();
// Can log whatever stuff from here in a single spot.
}


@AfterThrowing(pointcut = "execution(@co.xyz.aspect.EnableLogging * *(..)) && @annotation(enableLogging) && args(reqArg, reqArg1,..)", throwing="exception")
public void auditExceptionInfo(JoinPoint joinPoint, Throwable exception, EnableLogging enableLogging, Object reqArg, String reqArg1) {

    HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes())
            .getRequest();

    String requestUrl = request.getScheme() + "://" + request.getServerName()
    + ":" + request.getServerPort() + request.getContextPath() + request.getRequestURI()
    + "?" + request.getQueryString();

    exception.getMessage();
    exception.getCause();
    exception.printStackTrace();
    exception.getLocalizedMessage();
    // Can log whatever exceptions, requests, etc from here in a single spot.
    }
}

@AfterReturning-Ratschläge werden ausgeführt, wenn eine übereinstimmende Methodenausführung normal zurückgegeben wird.

@AfterThrowing-Ratschläge werden ausgeführt, wenn eine übereinstimmende Methodenausführung durch Auslösen einer Ausnahme beendet wird.

Wenn Sie im Detail lesen möchten, lesen Sie dies durch. http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html


1
Dadurch wird der Methodenaufruf protokolliert, nicht das, was auf HTTP-Ebene tatsächlich empfangen und gesendet wurde.
Tom Howard

1
Wie schreibe ich Anfrage BODY? In meinem Fall ist es POST BODY. Auf Anfrage.getReader oder getInputStream erhalte ich die Fehlermeldung, dass der Stream geschlossen ist.

13

Nach dem Hinzufügen von Aktoren zur Spring Boot Bassed-Anwendung steht Ihnen ein /traceEndpunkt mit den neuesten Anforderungsinformationen zur Verfügung. Dieser Endpunkt basiert auf TraceRepository und die Standardimplementierung ist InMemoryTraceRepository , das die letzten 100 Aufrufe speichert. Sie können dies ändern, indem Sie diese Schnittstelle selbst implementieren und als Spring Bean verfügbar machen. Zum Beispiel, um alle zu protokollierenden Anforderungen zu protokollieren (und weiterhin die Standardimplementierung als Basisspeicher zum Bereitstellen von Informationen auf dem /traceEndpunkt zu verwenden), verwende ich diese Art der Implementierung:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.actuate.trace.InMemoryTraceRepository;
import org.springframework.boot.actuate.trace.Trace;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;


@Component
public class LoggingTraceRepository implements TraceRepository {

  private static final Logger LOG = LoggerFactory.getLogger(LoggingTraceRepository.class);
  private final TraceRepository delegate = new InMemoryTraceRepository();

  @Override
  public List<Trace> findAll() {
    return delegate.findAll();
  }

  @Override
  public void add(Map<String, Object> traceInfo) {
    LOG.info(traceInfo.toString());
    this.delegate.add(traceInfo);
  }
}

Diese traceInfoKarte enthält grundlegende Informationen zu Anfrage und Antwort in folgender Form : {method=GET, path=/api/hello/John, headers={request={host=localhost:8080, user-agent=curl/7.51.0, accept=*/*}, response={X-Application-Context=application, Content-Type=text/plain;charset=UTF-8, Content-Length=10, Date=Wed, 29 Mar 2017 20:41:21 GMT, status=200}}}. Hier gibt es KEINEN Antwortinhalt.

BEARBEITEN! POST-Daten protokollieren

Sie können auf POST-Daten zugreifen , indem Sie WebRequestTraceFilter überschreiben. Dies ist jedoch keine gute Idee (z. B. werden alle hochgeladenen Dateiinhalte in Protokollen gespeichert). Hier ist Beispielcode, aber verwenden Sie ihn nicht :

package info.fingo.nuntius.acuate.trace;

import org.apache.commons.io.IOUtils;
import org.springframework.boot.actuate.trace.TraceProperties;
import org.springframework.boot.actuate.trace.TraceRepository;
import org.springframework.boot.actuate.trace.WebRequestTraceFilter;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.LinkedHashMap;
import java.util.Map;

@Component
public class CustomWebTraceFilter extends WebRequestTraceFilter {

  public CustomWebTraceFilter(TraceRepository repository, TraceProperties properties) {
    super(repository, properties);
}

  @Override
  protected Map<String, Object> getTrace(HttpServletRequest request) {
    Map<String, Object> trace = super.getTrace(request);
    String multipartHeader = request.getHeader("content-type");
    if (multipartHeader != null && multipartHeader.startsWith("multipart/form-data")) {
        Map<String, Object> parts = new LinkedHashMap<>();
        try {
            request.getParts().forEach(
                    part -> {
                        try {
                            parts.put(part.getName(), IOUtils.toString(part.getInputStream(), Charset.forName("UTF-8")));
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
            );
        } catch (IOException | ServletException e) {
            e.printStackTrace();
        }
        if (!parts.isEmpty()) {
            trace.put("multipart-content-map", parts);
        }
    }
    return trace;
  }
}

1
Was ist mit POST Körper?
Pavel Vyazankin

@dart Ich habe Beispiel für Sie hinzugefügt
Piotr Chowaniec

1
Ich habe so etwas gemacht, aber das Problem ist, dass der Antworttext nicht verfügbar TraceRepositoryist. Wie können wir darauf zugreifen?
Amir Pashazadeh

@AmirPashazadeh Sie müssen überschreiben, protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)aber ich bin nicht sicher, wann dieser Filter ausgeführt wird - möglicherweise in der Anforderungsphase, sodass der Antworttext dort nicht bereit ist.
Piotr Chowaniec

1
@ Kekar ​​Seit 2.0 gibt es HttpTraceRepository (anstelle von TraceRepository)
Piotr Chowaniec

12

Dieser Code funktioniert für mich in einer Spring Boot-Anwendung - registrieren Sie ihn einfach als Filter

    import java.io.BufferedReader;
    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.io.PrintWriter;
    import java.util.Collection;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.Locale;
    import java.util.Map;
    import javax.servlet.*;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import javax.servlet.http.HttpServletResponse;
    import org.apache.commons.io.output.TeeOutputStream;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;

    @Component
    public class HttpLoggingFilter implements Filter {

        private static final Logger log = LoggerFactory.getLogger(HttpLoggingFilter.class);

        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }

        @Override
        public void doFilter(ServletRequest request, ServletResponse response,
                             FilterChain chain) throws IOException, ServletException {
            try {
                HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                HttpServletResponse httpServletResponse = (HttpServletResponse) response;

                Map<String, String> requestMap = this
                        .getTypesafeRequestMap(httpServletRequest);
                BufferedRequestWrapper bufferedRequest = new BufferedRequestWrapper(
                        httpServletRequest);
                BufferedResponseWrapper bufferedResponse = new BufferedResponseWrapper(
                        httpServletResponse);

                final StringBuilder logMessage = new StringBuilder(
                        "REST Request - ").append("[HTTP METHOD:")
                        .append(httpServletRequest.getMethod())
                        .append("] [PATH INFO:")
                        .append(httpServletRequest.getServletPath())
                        .append("] [REQUEST PARAMETERS:").append(requestMap)
                        .append("] [REQUEST BODY:")
                        .append(bufferedRequest.getRequestBody())
                        .append("] [REMOTE ADDRESS:")
                        .append(httpServletRequest.getRemoteAddr()).append("]");

                chain.doFilter(bufferedRequest, bufferedResponse);
                logMessage.append(" [RESPONSE:")
                        .append(bufferedResponse.getContent()).append("]");
                log.debug(logMessage.toString());
            } catch (Throwable a) {
                log.error(a.getMessage());
            }
        }

        private Map<String, String> getTypesafeRequestMap(HttpServletRequest request) {
            Map<String, String> typesafeRequestMap = new HashMap<String, String>();
            Enumeration<?> requestParamNames = request.getParameterNames();
            while (requestParamNames.hasMoreElements()) {
                String requestParamName = (String) requestParamNames.nextElement();
                String requestParamValue;
                if (requestParamName.equalsIgnoreCase("password")) {
                    requestParamValue = "********";
                } else {
                    requestParamValue = request.getParameter(requestParamName);
                }
                typesafeRequestMap.put(requestParamName, requestParamValue);
            }
            return typesafeRequestMap;
        }

        @Override
        public void destroy() {
        }

        private static final class BufferedRequestWrapper extends
                HttpServletRequestWrapper {

            private ByteArrayInputStream bais = null;
            private ByteArrayOutputStream baos = null;
            private BufferedServletInputStream bsis = null;
            private byte[] buffer = null;

            public BufferedRequestWrapper(HttpServletRequest req)
                    throws IOException {
                super(req);
                // Read InputStream and store its content in a buffer.
                InputStream is = req.getInputStream();
                this.baos = new ByteArrayOutputStream();
                byte buf[] = new byte[1024];
                int read;
                while ((read = is.read(buf)) > 0) {
                    this.baos.write(buf, 0, read);
                }
                this.buffer = this.baos.toByteArray();
            }

            @Override
            public ServletInputStream getInputStream() {
                this.bais = new ByteArrayInputStream(this.buffer);
                this.bsis = new BufferedServletInputStream(this.bais);
                return this.bsis;
            }

            String getRequestBody() throws IOException {
                BufferedReader reader = new BufferedReader(new InputStreamReader(
                        this.getInputStream()));
                String line = null;
                StringBuilder inputBuffer = new StringBuilder();
                do {
                    line = reader.readLine();
                    if (null != line) {
                        inputBuffer.append(line.trim());
                    }
                } while (line != null);
                reader.close();
                return inputBuffer.toString().trim();
            }

        }

        private static final class BufferedServletInputStream extends
                ServletInputStream {

            private ByteArrayInputStream bais;

            public BufferedServletInputStream(ByteArrayInputStream bais) {
                this.bais = bais;
            }

            @Override
            public int available() {
                return this.bais.available();
            }

            @Override
            public int read() {
                return this.bais.read();
            }

            @Override
            public int read(byte[] buf, int off, int len) {
                return this.bais.read(buf, off, len);
            }

            @Override
            public boolean isFinished() {
                return false;
            }

            @Override
            public boolean isReady() {
                return true;
            }

            @Override
            public void setReadListener(ReadListener readListener) {

            }
        }

        public class TeeServletOutputStream extends ServletOutputStream {

            private final TeeOutputStream targetStream;

            public TeeServletOutputStream(OutputStream one, OutputStream two) {
                targetStream = new TeeOutputStream(one, two);
            }

            @Override
            public void write(int arg0) throws IOException {
                this.targetStream.write(arg0);
            }

            public void flush() throws IOException {
                super.flush();
                this.targetStream.flush();
            }

            public void close() throws IOException {
                super.close();
                this.targetStream.close();
            }

            @Override
            public boolean isReady() {
                return false;
            }

            @Override
            public void setWriteListener(WriteListener writeListener) {

            }
        }

        public class BufferedResponseWrapper implements HttpServletResponse {

            HttpServletResponse original;
            TeeServletOutputStream tee;
            ByteArrayOutputStream bos;

            public BufferedResponseWrapper(HttpServletResponse response) {
                original = response;
            }

            public String getContent() {
                return bos.toString();
            }

            public PrintWriter getWriter() throws IOException {
                return original.getWriter();
            }

            public ServletOutputStream getOutputStream() throws IOException {
                if (tee == null) {
                    bos = new ByteArrayOutputStream();
                    tee = new TeeServletOutputStream(original.getOutputStream(),
                            bos);
                }
                return tee;

            }

            @Override
            public String getCharacterEncoding() {
                return original.getCharacterEncoding();
            }

            @Override
            public String getContentType() {
                return original.getContentType();
            }

            @Override
            public void setCharacterEncoding(String charset) {
                original.setCharacterEncoding(charset);
            }

            @Override
            public void setContentLength(int len) {
                original.setContentLength(len);
            }

            @Override
            public void setContentLengthLong(long l) {
                original.setContentLengthLong(l);
            }

            @Override
            public void setContentType(String type) {
                original.setContentType(type);
            }

            @Override
            public void setBufferSize(int size) {
                original.setBufferSize(size);
            }

            @Override
            public int getBufferSize() {
                return original.getBufferSize();
            }

            @Override
            public void flushBuffer() throws IOException {
                tee.flush();
            }

            @Override
            public void resetBuffer() {
                original.resetBuffer();
            }

            @Override
            public boolean isCommitted() {
                return original.isCommitted();
            }

            @Override
            public void reset() {
                original.reset();
            }

            @Override
            public void setLocale(Locale loc) {
                original.setLocale(loc);
            }

            @Override
            public Locale getLocale() {
                return original.getLocale();
            }

            @Override
            public void addCookie(Cookie cookie) {
                original.addCookie(cookie);
            }

            @Override
            public boolean containsHeader(String name) {
                return original.containsHeader(name);
            }

            @Override
            public String encodeURL(String url) {
                return original.encodeURL(url);
            }

            @Override
            public String encodeRedirectURL(String url) {
                return original.encodeRedirectURL(url);
            }

            @SuppressWarnings("deprecation")
            @Override
            public String encodeUrl(String url) {
                return original.encodeUrl(url);
            }

            @SuppressWarnings("deprecation")
            @Override
            public String encodeRedirectUrl(String url) {
                return original.encodeRedirectUrl(url);
            }

            @Override
            public void sendError(int sc, String msg) throws IOException {
                original.sendError(sc, msg);
            }

            @Override
            public void sendError(int sc) throws IOException {
                original.sendError(sc);
            }

            @Override
            public void sendRedirect(String location) throws IOException {
                original.sendRedirect(location);
            }

            @Override
            public void setDateHeader(String name, long date) {
                original.setDateHeader(name, date);
            }

            @Override
            public void addDateHeader(String name, long date) {
                original.addDateHeader(name, date);
            }

            @Override
            public void setHeader(String name, String value) {
                original.setHeader(name, value);
            }

            @Override
            public void addHeader(String name, String value) {
                original.addHeader(name, value);
            }

            @Override
            public void setIntHeader(String name, int value) {
                original.setIntHeader(name, value);
            }

            @Override
            public void addIntHeader(String name, int value) {
                original.addIntHeader(name, value);
            }

            @Override
            public void setStatus(int sc) {
                original.setStatus(sc);
            }

            @SuppressWarnings("deprecation")
            @Override
            public void setStatus(int sc, String sm) {
                original.setStatus(sc, sm);
            }

            @Override
            public String getHeader(String arg0) {
                return original.getHeader(arg0);
            }

            @Override
            public Collection<String> getHeaderNames() {
                return original.getHeaderNames();
            }

            @Override
            public Collection<String> getHeaders(String arg0) {
                return original.getHeaders(arg0);
            }

            @Override
            public int getStatus() {
                return original.getStatus();
            }

        }
    }

Dies funktioniert gut für die Antwortprotokollierung - obwohl ich die Anzahl der zu protokollierenden Bytes begrenzen musste, da sonst die Ausgabe der Intellij-Protokollierungskonsole verworfen wird.
Adam

String getContent () {if (bos == null) {return String.format ("% s zu früh aufgerufen", BufferedResponseWrapper.class.getCanonicalName ()); } byte [] bytes = bos.toByteArray (); neuen String zurückgeben (Arrays.copyOf (Bytes, 5000)) + "...."; }
Adam

Es lohnt sich auch, einen "log.isTraceEnabled ()" - Schalter um die Protokollierung zu setzen.
Adam

6
Was cool wäre, wäre, wenn Java HttpServletResponse einige Standardmethoden hinzufügen würde, damit wir keine so große Implementierung schreiben müssen.
Adam

1
plus eine für die Aufnahme der Importanweisungen
GranadaCoder

7

Hier meine Lösung (Spring 2.0.x)

Fügen Sie die Maven-Abhängigkeit hinzu:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

Bearbeiten Sie die application.properties und fügen Sie die folgende Zeile hinzu:

management.endpoints.web.exposure.include=* 

Sobald Ihre Spring Boot-Anwendung gestartet ist, können Sie die letzten 100 http-Anforderungen verfolgen, indem Sie diese URL aufrufen: http: // localhost: 8070 / actuator / httptrace


7

Derzeit verfügt Spring Boot über die Actuator-Funktion, um die Protokolle von Anforderungen und Antworten abzurufen.

Sie können die Protokolle aber auch mit Aspect (AOP) abrufen.

Aspect bietet Ihnen mit Anmerkungen wie: @Before, @AfterReturning, @AfterThrowingusw.

@BeforeProtokolliert die Anforderung, @AfterReturningprotokolliert die Antwort und @AfterThrowingprotokolliert die Fehlermeldung. Möglicherweise benötigen Sie nicht das Protokoll aller Endpunkte, sodass Sie einige Filter auf die Pakete anwenden können.

Hier einige Beispiele :

Für Anfrage:

@Before("within(your.package.where.endpoints.are..*)")
    public void endpointBefore(JoinPoint p) {
        if (log.isTraceEnabled()) {
            log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " START");
            Object[] signatureArgs = p.getArgs();


            ObjectMapper mapper = new ObjectMapper();
            mapper.enable(SerializationFeature.INDENT_OUTPUT);
            try {

                if (signatureArgs[0] != null) {
                    log.trace("\nRequest object: \n" + mapper.writeValueAsString(signatureArgs[0]));
                }
            } catch (JsonProcessingException e) {
            }
        }
    }

Hier @Before("within(your.package.where.endpoints.are..*)")hat der Paketpfad. Alle Endpunkte in diesem Paket generieren das Protokoll.

Zur Antwort:

@AfterReturning(value = ("within(your.package.where.endpoints.are..*)"),
            returning = "returnValue")
    public void endpointAfterReturning(JoinPoint p, Object returnValue) {
        if (log.isTraceEnabled()) {
            ObjectMapper mapper = new ObjectMapper();
            mapper.enable(SerializationFeature.INDENT_OUTPUT);
            try {
                log.trace("\nResponse object: \n" + mapper.writeValueAsString(returnValue));
            } catch (JsonProcessingException e) {
                System.out.println(e.getMessage());
            }
            log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " END");
        }
    }

Hier @AfterReturning("within(your.package.where.endpoints.are..*)")hat der Paketpfad. Alle Endpunkte in diesem Paket generieren das Protokoll. Auch Object returnValueenthält die Antwort.

Ausnahme:

@AfterThrowing(pointcut = ("within(your.package.where.endpoints.are..*)"), throwing = "e")
public void endpointAfterThrowing(JoinPoint p, Exception e) throws DmoneyException {
    if (log.isTraceEnabled()) {
        System.out.println(e.getMessage());

        e.printStackTrace();


        log.error(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " " + e.getMessage());
    }
}

Hier @AfterThrowing(pointcut = ("within(your.package.where.endpoints.are..*)"), throwing = "e") hat der Paketpfad. Alle Endpunkte in diesem Paket generieren das Protokoll. Auch Exception eenthält die Fehlerreaktion.

Hier ist der vollständige Code:

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Aspect
@Order(1)
@Component
@ConditionalOnExpression("${endpoint.aspect.enabled:true}")
public class EndpointAspect {
    static Logger log = Logger.getLogger(EndpointAspect.class);

    @Before("within(your.package.where.is.endpoint..*)")
    public void endpointBefore(JoinPoint p) {
        if (log.isTraceEnabled()) {
            log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " START");
            Object[] signatureArgs = p.getArgs();


            ObjectMapper mapper = new ObjectMapper();
            mapper.enable(SerializationFeature.INDENT_OUTPUT);
            try {

                if (signatureArgs[0] != null) {
                    log.trace("\nRequest object: \n" + mapper.writeValueAsString(signatureArgs[0]));
                }
            } catch (JsonProcessingException e) {
            }
        }
    }

    @AfterReturning(value = ("within(your.package.where.is.endpoint..*)"),
            returning = "returnValue")
    public void endpointAfterReturning(JoinPoint p, Object returnValue) {
        if (log.isTraceEnabled()) {
            ObjectMapper mapper = new ObjectMapper();
            mapper.enable(SerializationFeature.INDENT_OUTPUT);
            try {
                log.trace("\nResponse object: \n" + mapper.writeValueAsString(returnValue));
            } catch (JsonProcessingException e) {
                System.out.println(e.getMessage());
            }
            log.trace(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " END");
        }
    }


    @AfterThrowing(pointcut = ("within(your.package.where.is.endpoint..*)"), throwing = "e")
    public void endpointAfterThrowing(JoinPoint p, Exception e) throws Exception {
        if (log.isTraceEnabled()) {
            System.out.println(e.getMessage());

            e.printStackTrace();


            log.error(p.getTarget().getClass().getSimpleName() + " " + p.getSignature().getName() + " " + e.getMessage());
        }
    }
}

Hier können @ConditionalOnExpression("${endpoint.aspect.enabled:true}")Sie das Protokoll aktivieren / deaktivieren. fügen Sie einfach endpoint.aspect.enabled:truein die application.propertyund das Protokoll steuern

Weitere Infos zum AOP-Besuch hier:

Spring Docks über AOP

Beispielartikel über AOP


1
new ObjectMapper()ist teuer, teilen Sie besser einen Mapper für alle
Sam

Ja sicher. Dies ist Demo-Code. In der Produktion müssen wir Best Practices befolgen.
Md. Sajedul Karim

5

Sie können auch einen benutzerdefinierten Spring-Interceptor HandlerInterceptorAdapterfür eine vereinfachte Implementierung von Pre-Only- / Post-Only-Interceptors konfigurieren :

@Component
public class CustomHttpInterceptor extends HandlerInterceptorAdapter {

    @Override
    public boolean preHandle (final HttpServletRequest request, final HttpServletResponse response,
            final Object handler)
            throws Exception {

        // Logs here

        return super.preHandle(request, response, handler);
    }

    @Override
    public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response,
            final Object handler, final Exception ex) {
        // Logs here
    }
}

Dann registrieren Sie so viele Abfangjäger, wie Sie möchten:

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {

    @Autowired
    CustomHttpInterceptor customHttpInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(customHttpInterceptor).addPathPatterns("/endpoints");
    }

}

Hinweis: Wie von @Robert angegeben , müssen Sie auf die spezifischen Implementierungen von und Ihre Anwendung achten . HttpServletRequestHttpServletResponse

Für Apps, die das verwenden ShallowEtagHeaderFilter, lautet die Antwortimplementierung beispielsweise a ContentCachingResponseWrapper, sodass Sie Folgendes haben:

@Component
public class CustomHttpInterceptor extends HandlerInterceptorAdapter {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomHttpInterceptor.class);

    private static final int MAX_PAYLOAD_LENGTH = 1000;

    @Override
    public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response,
            final Object handler, final Exception ex) {
        final byte[] contentAsByteArray = ((ContentCachingResponseWrapper) response).getContentAsByteArray();

        LOGGER.info("Request body:\n" + getContentAsString(contentAsByteArray, response.getCharacterEncoding()));
    }

    private String getContentAsString(byte[] buf, String charsetName) {
        if (buf == null || buf.length == 0) {
            return "";
        }

        try {
            int length = Math.min(buf.length, MAX_PAYLOAD_LENGTH);

            return new String(buf, 0, length, charsetName);
        } catch (UnsupportedEncodingException ex) {
            return "Unsupported Encoding";
        }
    }

}

4

@ hahns Antwort erforderte ein wenig Modifikation, damit es für mich funktioniert, aber es ist bei weitem das anpassbarste, was ich bekommen konnte.

Es hat bei mir nicht funktioniert, wahrscheinlich weil ich auch einen HandlerInterceptorAdapter [??] habe, aber in dieser Version erhielt ich immer wieder eine schlechte Antwort vom Server. Hier ist meine Modifikation davon.

public class LoggableDispatcherServlet extends DispatcherServlet {

    private final Log logger = LogFactory.getLog(getClass());

    @Override
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {

        long startTime = System.currentTimeMillis();
        try {
            super.doDispatch(request, response);
        } finally {
            log(new ContentCachingRequestWrapper(request), new ContentCachingResponseWrapper(response),
                    System.currentTimeMillis() - startTime);
        }
    }

    private void log(HttpServletRequest requestToCache, HttpServletResponse responseToCache, long timeTaken) {
        int status = responseToCache.getStatus();
        JsonObject jsonObject = new JsonObject();
        jsonObject.addProperty("httpStatus", status);
        jsonObject.addProperty("path", requestToCache.getRequestURI());
        jsonObject.addProperty("httpMethod", requestToCache.getMethod());
        jsonObject.addProperty("timeTakenMs", timeTaken);
        jsonObject.addProperty("clientIP", requestToCache.getRemoteAddr());
        if (status > 299) {
            String requestBody = null;
            try {
                requestBody = requestToCache.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
            } catch (IOException e) {
                e.printStackTrace();
            }
            jsonObject.addProperty("requestBody", requestBody);
            jsonObject.addProperty("requestParams", requestToCache.getQueryString());
            jsonObject.addProperty("tokenExpiringHeader",
                    responseToCache.getHeader(ResponseHeaderModifierInterceptor.HEADER_TOKEN_EXPIRING));
        }
        logger.info(jsonObject);
    }
}

Ist Ihre Anwendung als Krieg oder Glas verpackt? Ich erhalte immer wieder den Fehler java.io.FileNotFoundException: ServletContext-Ressource [/WEB-INF/loggingDispatcherServlet-servlet.xml] konnte nicht geöffnet werden
Mayank Madhav

4

Wenn jemand es hier noch braucht, ist eine einfache Implementierung mit Spring HttpTrace Actuator. Aber wie sie oben gesagt haben, protokolliert es keine Körper.

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.springframework.boot.actuate.trace.http.HttpTrace;
import org.springframework.boot.actuate.trace.http.InMemoryHttpTraceRepository;
import org.springframework.stereotype.Repository;

@Slf4j
@Repository
public class LoggingInMemoryHttpTraceRepository extends InMemoryHttpTraceRepository {
    public void add(HttpTrace trace) {
        super.add(trace);
        log.info("Trace:" + ToStringBuilder.reflectionToString(trace));
        log.info("Request:" + ToStringBuilder.reflectionToString(trace.getRequest()));
        log.info("Response:" + ToStringBuilder.reflectionToString(trace.getResponse()));
    }
}

4

Die aktuelle Antwort finden Sie unter dem folgenden Link: https://gist.github.com/int128/e47217bebdb4c402b2ffa7cc199307ba

Bei einigen Änderungen gegenüber der oben genannten Lösung werden Anforderung und Antwort in der Konsole und auch in der Datei protokolliert, wenn die Protokollierungsstufe info ist. Wir können entweder in der Konsole oder in der Datei drucken.

@Component
public class LoggingFilter extends OncePerRequestFilter {

private static final List<MediaType> VISIBLE_TYPES = Arrays.asList(
        MediaType.valueOf("text/*"),
        MediaType.APPLICATION_FORM_URLENCODED,
        MediaType.APPLICATION_JSON,
        MediaType.APPLICATION_XML,
        MediaType.valueOf("application/*+json"),
        MediaType.valueOf("application/*+xml"),
        MediaType.MULTIPART_FORM_DATA
        );
Logger log = LoggerFactory.getLogger(ReqAndResLoggingFilter.class);
private static final Path path = Paths.get("/home/ramesh/loggerReq.txt");
private static BufferedWriter writer = null;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
    try {
        writer = Files.newBufferedWriter(path, Charset.forName("UTF-8"));
    if (isAsyncDispatch(request)) {
        filterChain.doFilter(request, response);
    } else {
        doFilterWrapped(wrapRequest(request), wrapResponse(response), filterChain);
    }
    }finally {
        writer.close();
    }
}

protected void doFilterWrapped(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response, FilterChain filterChain) throws ServletException, IOException {
    try {
        beforeRequest(request, response);
        filterChain.doFilter(request, response);
    }
    finally {
        afterRequest(request, response);
        response.copyBodyToResponse();
    }
}

protected void beforeRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) throws IOException {
    if (log.isInfoEnabled()) {
        logRequestHeader(request, request.getRemoteAddr() + "|>");
    }
}

protected void afterRequest(ContentCachingRequestWrapper request, ContentCachingResponseWrapper response) throws IOException {
    if (log.isInfoEnabled()) {
        logRequestBody(request, request.getRemoteAddr() + "|>");
        logResponse(response, request.getRemoteAddr() + "|<");
    }
}

private void logRequestHeader(ContentCachingRequestWrapper request, String prefix) throws IOException {
    String queryString = request.getQueryString();
    if (queryString == null) {
        printLines(prefix,request.getMethod(),request.getRequestURI());
        log.info("{} {} {}", prefix, request.getMethod(), request.getRequestURI());
    } else {
        printLines(prefix,request.getMethod(),request.getRequestURI(),queryString);
        log.info("{} {} {}?{}", prefix, request.getMethod(), request.getRequestURI(), queryString);
    }
    Collections.list(request.getHeaderNames()).forEach(headerName ->
    Collections.list(request.getHeaders(headerName)).forEach(headerValue ->
    log.info("{} {}: {}", prefix, headerName, headerValue)));
    printLines(prefix);
    printLines(RequestContextHolder.currentRequestAttributes().getSessionId());
    log.info("{}", prefix);

    log.info(" Session ID: ", RequestContextHolder.currentRequestAttributes().getSessionId());
}

private void printLines(String ...args) throws IOException {

    try {
    for(String varArgs:args) {
            writer.write(varArgs);
            writer.newLine();
    }
        }catch(IOException ex){
            ex.printStackTrace();
    }

}

private void logRequestBody(ContentCachingRequestWrapper request, String prefix) {
    byte[] content = request.getContentAsByteArray();
    if (content.length > 0) {
        logContent(content, request.getContentType(), request.getCharacterEncoding(), prefix);
    }
}

private void logResponse(ContentCachingResponseWrapper response, String prefix) throws IOException {
    int status = response.getStatus();
    printLines(prefix, String.valueOf(status), HttpStatus.valueOf(status).getReasonPhrase());
    log.info("{} {} {}", prefix, status, HttpStatus.valueOf(status).getReasonPhrase());
    response.getHeaderNames().forEach(headerName ->
    response.getHeaders(headerName).forEach(headerValue ->
    log.info("{} {}: {}", prefix, headerName, headerValue)));
    printLines(prefix);
    log.info("{}", prefix);
    byte[] content = response.getContentAsByteArray();
    if (content.length > 0) {
        logContent(content, response.getContentType(), response.getCharacterEncoding(), prefix);
    }
}

private void logContent(byte[] content, String contentType, String contentEncoding, String prefix) {
    MediaType mediaType = MediaType.valueOf(contentType);
    boolean visible = VISIBLE_TYPES.stream().anyMatch(visibleType -> visibleType.includes(mediaType));
    if (visible) {
        try {
            String contentString = new String(content, contentEncoding);
            Stream.of(contentString.split("\r\n|\r|\n")).forEach(line -> {
                try {
                    printLines(line);
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            });
//              log.info("{} {}", prefix, line));
        } catch (UnsupportedEncodingException e) {
            log.info("{} [{} bytes content]", prefix, content.length);
        }
    } else {

        log.info("{} [{} bytes content]", prefix, content.length);
    }
}

private static ContentCachingRequestWrapper wrapRequest(HttpServletRequest request) {
    if (request instanceof ContentCachingRequestWrapper) {
        return (ContentCachingRequestWrapper) request;
    } else {
        return new ContentCachingRequestWrapper(request);
    }
}

private static ContentCachingResponseWrapper wrapResponse(HttpServletResponse response) {
    if (response instanceof ContentCachingResponseWrapper) {
        return (ContentCachingResponseWrapper) response;
    } else {
        return new ContentCachingResponseWrapper(response);
    }
}
} 

Ausgabe in Datei:

127.0.0.1|>
POST
/createUser
127.0.0.1|>
session Id:C0793464532E7F0C7154913CBA018B2B
Request:
{
  "name": "asdasdas",
  "birthDate": "2018-06-21T17:11:15.679+0000"
}
127.0.0.1|<
200
OK
127.0.0.1|<
Response:
{"name":"asdasdas","birthDate":"2018-06-21T17:11:15.679+0000","id":4}

1
Gute Antwort, nur ein Vorschlag wäre, die gesamte Ausgabe in einem Puffer zu sammeln und sich in einer einzigen Anweisung anzumelden.
Mike

2

Wenn Sie nur einen Teil Ihrer Anforderungsnutzlast sehen, müssen Sie die setMaxPayloadLengthFunktion aufrufen , da standardmäßig nur 50 Zeichen in Ihrem Anforderungshauptteil angezeigt werden. Die Einstellung setIncludeHeadersauf false ist auch eine gute Idee, wenn Sie Ihre Auth-Header nicht protokollieren möchten!

@Bean
public CommonsRequestLoggingFilter requestLoggingFilter() {
    CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();
    loggingFilter.setIncludeClientInfo(false);
    loggingFilter.setIncludeQueryString(false);
    loggingFilter.setIncludePayload(true);
    loggingFilter.setIncludeHeaders(false);
    loggingFilter.setMaxPayloadLength(500);
    return loggingFilter;
}

Ich versuche es im Frühjahr mvc zu verwenden und es funktioniert nicht für mich. Es ist eine zusätzliche Einstellung erforderlich, außer diese Bean zu registrieren und Logger hinzuzufügen?
Noman Akhtar

1

Wenn Sie Tomcat in Ihrer Boot-App verwenden, befindet sich hier org.apache.catalina.filters.RequestDumperFilterein Klassenpfad für Sie. (aber es wird Ihnen nicht "Ausnahmen an einem Ort" geben).


1

Der unten eingefügte Code funktioniert mit meinen Tests und kann von meinem [Github-Projekt] [1] heruntergeladen werden, nachdem eine auf diesem Produktionsprojekt basierende Lösung angewendet wurde.

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

    }
}

0

Um alle Anforderungen mit Eingabeparametern und Body zu protokollieren, können wir Filter und Interceptors verwenden . Bei Verwendung eines Filters oder Interceptors können wir den Anforderungshauptteil jedoch nicht mehrmals drucken. Der bessere Weg ist, dass wir Spring-AOP verwenden können. Auf diese Weise können wir den Protokollierungsmechanismus von der Anwendung entkoppeln. AOP kann für die Protokollierung verwendet werden Eingang und Ausgang von jeder Methode in der Anwendung.

Meine Lösung ist:

 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.annotation.Around;
 import org.aspectj.lang.annotation.Aspect;
 import org.aspectj.lang.annotation.Pointcut;
 import org.aspectj.lang.reflect.CodeSignature;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.stereotype.Component;
 import com.fasterxml.jackson.databind.ObjectMapper;
 @Aspect
 @Component
public class LoggingAdvice {
private static final Logger logger = 
LoggerFactory.getLogger(LoggingAdvice.class);

//here we can provide any methodName, packageName, className 
@Pointcut(value = "execution(* com.package.name.*.*.*(..) )")
public void myPointcut() {

}

@Around("myPointcut()")
public Object applicationLogger(ProceedingJoinPoint pjt) throws Throwable {
    ObjectMapper mapper = new ObjectMapper();
    String methodName = pjt.getSignature().getName();
    String className = pjt.getTarget().getClass().toString();
    String inputParams = this.getInputArgs(pjt ,mapper);
    logger.info("method invoked from " + className + " : " + methodName + "--Request Payload::::"+inputParams);
    Object object = pjt.proceed();
    try {
        logger.info("Response Object---" + mapper.writeValueAsString(object));
    } catch (Exception e) {
    }
    return object;
}

private String getInputArgs(ProceedingJoinPoint pjt,ObjectMapper mapper) {
    Object[] array = pjt.getArgs();
    CodeSignature signature = (CodeSignature) pjt.getSignature();

    StringBuilder sb = new StringBuilder();
    sb.append("{");
    int i = 0;
    String[] parameterNames = signature.getParameterNames();
    int maxArgs = parameterNames.length;
    for (String name : signature.getParameterNames()) {
        sb.append("[").append(name).append(":");
        try {
            sb.append(mapper.writeValueAsString(array[i])).append("]");
            if(i != maxArgs -1 ) {
                sb.append(",");
            }
        } catch (Exception e) {
            sb.append("],");
        }
        i++;
    }
    return sb.append("}").toString();
}

}}


0

Wenn Sie den Spring Boot Config-Server konfiguriert haben, aktivieren Sie einfach den Debug-Logger für die Klasse:

Http11InputBuffer.Http11InputBuffer.java

Debugs protokollieren alle Anforderungen und Antworten für jede Anforderung


-1

So protokollieren Sie Anforderungen, die nur zu 400 führen:

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.apache.commons.io.FileUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.AbstractRequestLoggingFilter;
import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.util.ContentCachingRequestWrapper;
import org.springframework.web.util.WebUtils;

/**
 * Implementation is partially copied from {@link AbstractRequestLoggingFilter} and modified to output request information only if request resulted in 400.
 * Unfortunately {@link AbstractRequestLoggingFilter} is not smart enough to expose {@link HttpServletResponse} value in afterRequest() method.
 */
@Component
public class RequestLoggingFilter extends OncePerRequestFilter {

    public static final String DEFAULT_AFTER_MESSAGE_PREFIX = "After request [";

    public static final String DEFAULT_AFTER_MESSAGE_SUFFIX = "]";

    private final boolean includeQueryString = true;
    private final boolean includeClientInfo = true;
    private final boolean includeHeaders = true;
    private final boolean includePayload = true;

    private final int maxPayloadLength = (int) (2 * FileUtils.ONE_MB);

    private final String afterMessagePrefix = DEFAULT_AFTER_MESSAGE_PREFIX;

    private final String afterMessageSuffix = DEFAULT_AFTER_MESSAGE_SUFFIX;

    /**
     * The default value is "false" so that the filter may log a "before" message
     * at the start of request processing and an "after" message at the end from
     * when the last asynchronously dispatched thread is exiting.
     */
    @Override
    protected boolean shouldNotFilterAsyncDispatch() {
        return false;
    }

    @Override
    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
            throws ServletException, IOException {

        final boolean isFirstRequest = !isAsyncDispatch(request);
        HttpServletRequest requestToUse = request;

        if (includePayload && isFirstRequest && !(request instanceof ContentCachingRequestWrapper)) {
            requestToUse = new ContentCachingRequestWrapper(request, maxPayloadLength);
        }

        final boolean shouldLog = shouldLog(requestToUse);

        try {
            filterChain.doFilter(requestToUse, response);
        } finally {
            if (shouldLog && !isAsyncStarted(requestToUse)) {
                afterRequest(requestToUse, response, getAfterMessage(requestToUse));
            }
        }
    }

    private String getAfterMessage(final HttpServletRequest request) {
        return createMessage(request, this.afterMessagePrefix, this.afterMessageSuffix);
    }

    private String createMessage(final HttpServletRequest request, final String prefix, final String suffix) {
        final StringBuilder msg = new StringBuilder();
        msg.append(prefix);
        msg.append("uri=").append(request.getRequestURI());

        if (includeQueryString) {
            final String queryString = request.getQueryString();
            if (queryString != null) {
                msg.append('?').append(queryString);
            }
        }

        if (includeClientInfo) {
            final String client = request.getRemoteAddr();
            if (StringUtils.hasLength(client)) {
                msg.append(";client=").append(client);
            }
            final HttpSession session = request.getSession(false);
            if (session != null) {
                msg.append(";session=").append(session.getId());
            }
            final String user = request.getRemoteUser();
            if (user != null) {
                msg.append(";user=").append(user);
            }
        }

        if (includeHeaders) {
            msg.append(";headers=").append(new ServletServerHttpRequest(request).getHeaders());
        }

        if (includeHeaders) {
            final ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
            if (wrapper != null) {
                final byte[] buf = wrapper.getContentAsByteArray();
                if (buf.length > 0) {
                    final int length = Math.min(buf.length, maxPayloadLength);
                    String payload;
                    try {
                        payload = new String(buf, 0, length, wrapper.getCharacterEncoding());
                    } catch (final UnsupportedEncodingException ex) {
                        payload = "[unknown]";
                    }
                    msg.append(";payload=").append(payload);
                }
            }
        }
        msg.append(suffix);
        return msg.toString();
    }

    private boolean shouldLog(final HttpServletRequest request) {
        return true;
    }

    private void afterRequest(final HttpServletRequest request, final HttpServletResponse response, final String message) {
        if (response.getStatus() == HttpStatus.BAD_REQUEST.value()) {
            logger.warn(message);
        }
    }

}
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.