2015-11-16 23 views
65

Sto lavorando su api di riposo con avvio a molla. Ho bisogno di registrare tutte le richieste con parametri di input (con metodi, ad esempio GET, POST, ecc.), Percorso di richiesta, stringa di query, metodo di classe corrispondente di questa richiesta, anche la risposta di questa azione, sia il successo che gli errori.Spring Boot - Come registrare tutte le richieste e le risposte con eccezioni in un unico posto?

Per un esempio:

richiesta riuscita:

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

registro dovrebbe essere guardato qualcosa di simile:

{ 
    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: "[email protected]" 
     } 
    }, 
    exceptions: []  
} 

o richiesta con l'errore:

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

Log dovrebbe b E qualcosa del genere:

{ 
     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: ...................  
     ]  
    } 

Desidero che Richiesta/Risposta sia una singola entità, con informazioni personalizzate relative a questa entità, sia in casi riusciti che in casi di errore.

Qual è la migliore pratica in primavera per ottenere ciò, potrebbe essere con i filtri? se sì, puoi fornire un esempio concreto?

(Ho giocato con @ControllerAdvice e @ExceptionHandler, ma come ho già detto, devo gestire tutte le richieste di errore e di successo in un unico posto (e un singolo registro)).

+0

Probabilmente tramite un ServletFilter di registrazione (ad esempio http://stackoverflow.com/a/2171633/995891), in alternativa "HandlerInterceptor", ma potrebbe non funzionare correttamente con la registrazione della risposta, come indicato nella risposta: http: // www .concretepage.com/spring/spring-mvc/spring-handlerinterceptor-annotation-example-webmvcconfigureradapter - HandlerInterceptor ha comunque accesso al metodo (metodo: "UsersController.getUser"). Questo non è noto in un filtro servlet. – zapl

+0

ancora, anche se si aggiunge un filtro o qualsiasi altra soluzione a livello di applicazione, non si registrerà tutta la richiesta, ad es. l'Errore HTTP 500 Server non verrà registrato, poiché nel momento in cui un'eccezione non gestita verrà lanciata al livello Applicazione, verrà visualizzata la pagina di errore incorporata del tomcat predefinito dopo l'ingerenza dell'eccezione e naturalmente non verrà conservato il registro. Inoltre, se si verifica la risposta dell'utente 1817243, in caso di qualsiasi eccezione egli non registrerà nuovamente la richiesta ma registrerà l'eccezione (!!). – AntJavaDev

+0

Questo formato di log deve essere coerente con ogni carattere che hai scritto? Sembra che una traduzione JSON sia ottimale nel tuo caso: 'LogClass {getRequestAndSaveIt()} Gson.toJson (LogClass)' come pseudocode – Vale

risposta

3

Questo codice funziona per me in un'applicazione primavera Boot - basta registrarlo come un filtro

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(); 
      } 

     } 
    } 
+0

Funziona bene per la registrazione delle risposte - anche se ho dovuto porre un limite al numero di byte che registra altrimenti cancella l'output della console di registrazione Intellij. – Adam

+0

String getContent() { if (bos == null) { return String.format ("chiamato% s troppo presto", BufferedResponseWrapper.class.getCanonicalName()); } byte [] bytes = bos.toByteArray(); return new String (Arrays.copyOf (byte, 5000)) + "...."; } – Adam

+0

Vale anche la pena inserire in un "log.isTraceEnabled()" anche il passaggio alla registrazione. – Adam

11

Se non vi dispiace cercando Spring AOP, questo è qualcosa che ho esplorato a fini di registrazione e funziona abbastanza bene per me. Non registrerà richieste che non sono state definite e tentativi di richiesta falliti.

aggiungere questi tre dipendenze

spring-aop, aspectjrt, aspectjweaver 

aggiungere questo al tuo XML file di configurazione <aop:aspectj-autoproxy/>

creare un'annotazione che può essere usato come un pointcut

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

Ora annotare tutte le vostre API REST metodi che si desidera registrare

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

Ora sull'aspetto. componente scansione del pacchetto che questa classe è in.

@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 advice runs when a matched method execution returns normally.

@AfterThrowing advice runs when a matched method execution exits by throwing an exception.

Se si desidera leggere in dettaglio lettura di questo. http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html

+1

Registra il richiamo del metodo, non ciò che è effettivamente ricevuto e inviato a livello HTTP. –

+0

Come scrivere la richiesta BODY? Nel mio caso è POST BODY. su request.getReader o getInputStream ottengo l'errore che il flusso è chiuso. –

19

È possibile utilizzare javax.servlet.Filter se non è stato necessario registrare il metodo java che è stato eseguito.

Ma con questo requisito è necessario accedere alle informazioni memorizzate in handlerMapping di DispatcherServlet.Detto questo, è possibile ignorare lo DispatcherServlet per eseguire la registrazione della coppia richiesta/risposta.

Di seguito è riportato un esempio di idea che può essere ulteriormente migliorato e adottato per le vostre esigenze.

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 - contiene le informazioni su richiesta del gestore.

È quindi possibile registrare questo dispatcher come segue:

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

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

Ed ecco il campione di tronchi:

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"}'} 

UPDATE

In caso di errori di primavera fa la gestione automatica degli errori . Pertanto, BasicErrorController#error viene visualizzato come gestore richieste. Se si desidera conservare il gestore di richieste originale, è possibile ignorare questo comportamento a spring-webmvc-4.2.5.RELEASE-sources.jar!/org/springframework/web/servlet/DispatcherServlet.java:971 prima di chiamare il numero #processDispatchResult per memorizzare il gestore originale nella cache.

+0

cosa succede quando la risposta è uno stream e lo stream non supporta seek? Funzionerà ancora sopra? –

+0

Non mi interessa il metodo invocato, solo i dati ricevuti e inviati. Filtro sembra indicarmi la giusta direzione e la risposta di @ ike_love mi ha indirizzato a https://github.com/spring-projects/spring-boot/blob/master/spring-boot-actuator/src/main/java/org /springframework/boot/actuate/trace/WebRequestTraceFilter.java –

+0

@TomHoward AFAIK, non c'è "registrazione delle risposte" pronta per l'uso in primavera. Pertanto è possibile estendere WebRequestTraceFilter o AbstractRequestLoggingFilter aggiungendo la logica di registrazione delle risposte. – hahn

40

Non scrivere intercettori, filtri, componenti, aspetti ecc., Questo è un problema molto comune ed è stato risolto molte volte. Spring Boot ha un modulo chiamato Actuator che fornisce la registrazione della richiesta HTTP fuori dalla scatola. C'è un endpoint mappato a /trace che mostrerà le ultime richieste HTTP. È possibile personalizzarlo per registrare ogni richiesta o scrivere in un DB.

Inoltre, dove verrà eseguita questa applicazione? Utilizzerai un PaaS? I provider di hosting, ad esempio Heroku, forniscono la registrazione delle richieste come parte del loro servizio e non è necessario eseguire qualsiasi tipo di codifica.

+2

altri dettagli? Ho trovato https://github.com/spring-projects/spring-boot/tree/master/spring-boot-actuator/src/main/java/org/springframework/boot/actuate/trace, ma non molto oltre . –

+0

Ed è solo per la richiesta, o anche per la risposta? –

+0

@TomHoward tutte le implementazioni che prevedono la primavera considerano solo la richiesta. – hahn

2

@hahn's answer richiesto un po 'di modifica per farlo funzionare per me, ma è di gran lunga la cosa più personalizzabile che potrei ottenere.

Non ha funzionato per me, probabilmente perché ho anche un HandlerInterceptorAdapter [??] ma ho ricevuto una cattiva risposta dal server in quella versione. Ecco la mia modifica di esso.

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); 
    } 
} 
12

Ecco come lo faccio nei dati di primavera resto utilizzando org.springframework.web.util.ContentCachingRequestWrapper e 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 

    } 


} 
+2

funziona con risposte asincrone? –

7

Dopo aver aggiunto Actuators all'applicazione bassed avvio primavera avete /trace endpoint disponibile con tutte le richieste informazioni . Questo endpoint funziona in base a TraceRepository e l'implementazione predefinita è InMemoryTraceRepository che salva le ultime 100 chiamate. Puoi cambiarlo implementando questa interfaccia da solo e rendilo disponibile come bean Spring. Ad esempio, per registrare tutte le richieste per accedere (e utilizzare ancora implementazione predefinita come archiviazione di base per il servizio informazioni /trace endpoint) Sto usando questo tipo di implementazione:

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

Questo traceInfo mappa contiene informazioni di base su richiesta e risposta in questo tipo di modulo: {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}}}. Non ci sono contenuti di risposta qui.

MODIFICA! registrazione dei dati POST

È possibile accedere ai dati POST sovrascrivendo WebRequestTraceFilter, ma non credo che sia una buona idea (ad esempio, tutti i contenuti file caricato andrà al log) Ecco il codice di esempio, ma no usarlo:

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; 
    } 
} 
+0

E il corpo POST? – dart

+0

@dart Ho aggiunto un esempio per te –

+1

Stavo facendo qualcosa del genere, ma il problema è che il corpo della risposta non è disponibile per 'TraceRepository', come possiamo accedervi? –

31

Primavera fornisce già un filtro che fa questo lavoro. Aggiungere seguente chicco alla propria configurazione

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

Non dimenticare di modificare il livello di log di org.springframework.web.filter.CommonsRequestLoggingFilter-DEBUG.

+16

Nota che non _non_ registra le risposte, solo le richieste. –

+1

Vi sono solo richieste. Come registrare gli organismi di risposta utilizzando CommonsRequestLoggingFilter? – user2602807

+0

Anche questo non registra Eccezione – BhendiGawaar

1

avevo definito livello di registrazione in application.properties stampare le richieste/risposte, il metodo url nel file di registro

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

avevo usato primavera Boot.

+2

Sì, hai ragione - questa è una risposta valida per ottenere richieste accedere allo stesso file di registro con tutti gli altri risultati. Tuttavia, @moreo ha chiesto di accedere a GET, POST, ecc. E al file separato (come ho capito) –

1

se si utilizza Tomcat nell'app di avvio qui è org.apache.catalina.filters.RequestDumperFilter in un percorso di classe per l'utente. (ma non ti fornirà "con eccezioni in un unico posto").

4

La libreria Logbook è specifica per la registrazione di richieste e risposte HTTP. Supporta Spring Boot utilizzando una speciale libreria di avvio.

Per abilitare la registrazione in Spring Boot è sufficiente aggiungere la libreria alle dipendenze del progetto. Per esempio si supponendo che si utilizza Maven:

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

Per impostazione predefinita l'output di registrazione si presenta così:

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

Lo fa, tuttavia, non in uscita il nome della classe che sta gestendo la richiesta. La libreria ha alcune interfacce per la scrittura di logger personalizzati.

Problemi correlati