春季启动 – 如何在一个地方logging所有请求和exception的例外情况?

我正在与春季启动restAPI。 我需要用input参数(方法,如GET,POST等),请求path,查询string,对应的这个请求的类方法,以及这个动作的响应,成功和错误logging所有的请求。

举个例子:

成功的要求:

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

日志应该看起来像这样:

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

或请求错误:

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

日志应该是这样的:

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

我希望请求/响应成为一个单一的实体,与成功和错误的情况下,与这个实体相关的自定义信息。

spring最好的做法是达到这个目的,可能是filter? 如果是的话,你能提供具体的例子吗?

(我已经玩过@ControllerAdvice和@ExceptionHandler,但正如我所提到的,我需要在单个地方(和单个日志)处理所有的成功和错误请求)。

不要写任何拦截器,filter,组件,方面等,这是一个非常普遍的问题,已经解决了很多次。 Spring Boot有一个称为Actuator的模块,它提供HTTP请求注销function。 有一个映射到/trace的端点会显示最后几个HTTP请求。 您可以对其进行自定义以logging每个请求,或写入数据库。

另外,这个应用程序在哪里运行? 你会使用PaaS吗? 主机提供商,例如Heroku,提供请求日志作为他们的服务的一部分,你不需要做任何编码。

Spring已经提供了一个完成这个工作的filter。 将以下bean添加到您的configuration中

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

不要忘记将org.springframework.web.filter.CommonsRequestLoggingFilter日志级别更改为DEBUG

如果没有要求logging已执行的java方法,则可以使用javax.servlet.Filter

但是有了这个需求,你必须访问存储在DispatcherServlet handlerMapping的信息。 也就是说,您可以重写DispatcherServlet来完成请求/响应对的logging。

以下是一个可以进一步增强和采纳您的需求的想法的例子。

 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 – 包含有关请求处理程序的信息。

然后你可以注册这个调度程序如下:

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

这里是日志的样本:

 http http://localhost:8090/settings/test igmssLoggableDispatcherServlet : 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 igmssLoggableDispatcherServlet : LogMessage{httpStatus=200, path='/settings/httpParams', httpMethod='GET', clientIp='127.0.0.1', javaMethod='HandlerExecutionChain with handler [public xyzDTO xyzController.params()] and 3 interceptors', arguments=null, response='{}'} http http://localhost:8090/123 igmssLoggableDispatcherServlet : 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

如果有错误,Spring会自动处理错误。 因此, BasicErrorController#error显示为请求处理程序。 如果你想保留原始的请求处理程序,那么你可以在spring-webmvc-4.2.5.RELEASE-sources.jar!/org/springframework/web/servlet/DispatcherServlet.java:971之前在spring-webmvc-4.2.5.RELEASE-sources.jar!/org/springframework/web/servlet/DispatcherServlet.java:971caching原始处理程序。

这里是我如何做到这一点在春季数据rest通过使用org.springframework.web.util.ContentCachingRequestWrapperorg.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 } } 

如果你不介意尝试Spring AOP,这是我一直在探索的目的,它对我来说工作得很好。 它不会logging尚未定义的请求,但尝试失败请求。

添加这三个依赖项

 spring-aop, aspectjrt, aspectjweaver 

添加到你的XMLconfiguration文件<aop:aspectj-autoproxy/>

创build一个可用作切入点的注释

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

现在注释你想要logging的所有restAPI方法

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

现在来看看。 组件扫描这个类所在的包。

 @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通知运行。

当匹配的方法执行通过抛出exception退出时,@AfterThrowing通知运行。

如果你想详细阅读通过这个阅读。 http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html

添加执行器到弹簧启动Bassed应用程序后,您有/trace端点可用最新的请求信息。 此端点基于TraceRepository进行工作,默认实现是InMemoryTraceRepository ,可保存最后100个调用。 你可以通过自己实现这个接口来改变它,并把它作为一个Spring bean来使用。 例如,要logging所有的请求(并仍然使用默认的实现作为服务信息/trace端点上的基本存储)我正在使用这种实现:

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

这个traceInfo映射包含这种forms的请求和响应的基本信息: {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}}} 。 这里没有回应的内容。

编辑! loggingPOST数据

您可以通过覆盖WebRequestTraceFilter来访问POST数据,但不要认为这是一个好主意(例如,所有上传的文件内容都将转到日志)。下面是示例代码,但不要使用它:

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

这个代码适用于Spring Boot应用程序 – 只需注册为一个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(); } } } 

@ hahn的答案需要一些修改才能为我工作,但是这是迄今为止我能得到的最可定制的东西。

它没有为我工作,可能是因为我也有一个HandlerInterceptorAdapter [??],但是我一直从该版本的服务器得到一个错误的响应。 这是我的修改。

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

我已经在application.properties定义了日志级别来打印请求/响应,日志文件中的方法url

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

我用过Spring Boot。

如果您在启动应用程序中使用Tomcat,则在类path中为org.apache.catalina.filters.RequestDumperFilter 。 (但它不会为您提供“在一个地方例外”)。