Spring Boot - 如何在一个地方记录所有具有异常的请求和响应?

我正在用弹簧靴做休息api。我需要使用输入参数(使用方法,例如GET,POST等),请求路径,查询字符串,此请求的相应类方法,以及此操作的响应(成功和错误)来记录所有请求。


举个例子:


成功要求:


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: ...................    

       ]       

    }

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


在春天实现这一目标的最佳做法是什么,可能是过滤器?如果是的话,你能提供具体的例子吗?


(我已经使用了@ControllerAdvice和@ExceptionHandler,但正如我所提到的,我需要在单个位置(和单个日志)处理所有成功和错误请求)。


万千封印
浏览 1219回答 3
3回答

MMMHUHU

javax.servlet.Filter如果没有要求记录已执行的java方法,则可以使用。但是有了这个要求,你必须访问存储在其中handlerMapping的信息DispatcherServlet。也就是说,您可以覆盖DispatcherServlet以完成请求/响应对的记录。以下是一个可以进一步增强和满足您需求的想法示例。public class LoggableDispatcherServlet extends DispatcherServlet {&nbsp; &nbsp; private final Log logger = LogFactory.getLog(getClass());&nbsp; &nbsp; @Override&nbsp; &nbsp; protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {&nbsp; &nbsp; &nbsp; &nbsp; if (!(request instanceof ContentCachingRequestWrapper)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; request = new ContentCachingRequestWrapper(request);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; if (!(response instanceof ContentCachingResponseWrapper)) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; response = new ContentCachingResponseWrapper(response);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; HandlerExecutionChain handler = getHandler(request);&nbsp; &nbsp; &nbsp; &nbsp; try {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; super.doDispatch(request, response);&nbsp; &nbsp; &nbsp; &nbsp; } finally {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; log(request, response, handler);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; updateResponse(response);&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }&nbsp; &nbsp; private void log(HttpServletRequest requestToCache, HttpServletResponse responseToCache, HandlerExecutionChain handler) {&nbsp; &nbsp; &nbsp; &nbsp; LogMessage log = new LogMessage();&nbsp; &nbsp; &nbsp; &nbsp; log.setHttpStatus(responseToCache.getStatus());&nbsp; &nbsp; &nbsp; &nbsp; log.setHttpMethod(requestToCache.getMethod());&nbsp; &nbsp; &nbsp; &nbsp; log.setPath(requestToCache.getRequestURI());&nbsp; &nbsp; &nbsp; &nbsp; log.setClientIp(requestToCache.getRemoteAddr());&nbsp; &nbsp; &nbsp; &nbsp; log.setJavaMethod(handler.toString());&nbsp; &nbsp; &nbsp; &nbsp; log.setResponse(getResponsePayload(responseToCache));&nbsp; &nbsp; &nbsp; &nbsp; logger.info(log);&nbsp; &nbsp; }&nbsp; &nbsp; private String getResponsePayload(HttpServletResponse response) {&nbsp; &nbsp; &nbsp; &nbsp; ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);&nbsp; &nbsp; &nbsp; &nbsp; if (wrapper != null) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; byte[] buf = wrapper.getContentAsByteArray();&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; if (buf.length > 0) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; int length = Math.min(buf.length, 5120);&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; try {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; return new String(buf, 0, length, wrapper.getCharacterEncoding());&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; catch (UnsupportedEncodingException ex) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // NOOP&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; return "[unknown]";&nbsp; &nbsp; }&nbsp; &nbsp; private void updateResponse(HttpServletResponse response) throws IOException {&nbsp; &nbsp; &nbsp; &nbsp; ContentCachingResponseWrapper responseWrapper =&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);&nbsp; &nbsp; &nbsp; &nbsp; responseWrapper.copyBodyToResponse();&nbsp; &nbsp; }}HandlerExecutionChain - 包含有关请求处理程序的信息。然后,您可以将此调度程序注册如下:&nbsp; &nbsp; @Bean&nbsp; &nbsp; public ServletRegistrationBean dispatcherRegistration() {&nbsp; &nbsp; &nbsp; &nbsp; return new ServletRegistrationBean(dispatcherServlet());&nbsp; &nbsp; }&nbsp; &nbsp; @Bean(name = DispatcherServletAutoConfiguration.DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)&nbsp; &nbsp; public DispatcherServlet dispatcherServlet() {&nbsp; &nbsp; &nbsp; &nbsp; return new LoggableDispatcherServlet();&nbsp; &nbsp; }这是日志的样本:http http://localhost:8090/settings/testi.g.m.s.s.LoggableDispatcherServlet&nbsp; &nbsp; &nbsp; : 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/paramsi.g.m.s.s.LoggableDispatcherServlet&nbsp; &nbsp; &nbsp; : 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/123i.g.m.s.s.LoggableDispatcherServlet&nbsp; &nbsp; &nbsp; : 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之前覆盖此行为#processDispatchResult,以缓存原始处理程序。

拉莫斯之舞

不要写任何拦截器,滤波器,组件,方面等,这是一个非常常见的问题,已经解决了很多次。Spring Boot有一个名为Actuator的模块,它提供开箱即用的HTTP请求记录。有一个端点映射到/trace(SB1.x)或/actuator/httptrace(SB2.0 +),它将显示最近100个HTTP请求。您可以自定义它以记录每个请求,或写入数据库。要获得所需的端点,您需要执行器springboot依赖项,并且还要“查找”您正在寻找的端点,并可能设置或禁用它的安全性。此外,此应用程序将在何处运行?你会使用PaaS吗?例如,主机提供商Heroku提供请求记录作为其服务的一部分,您无需进行任何编码。

UYOU

Spring已经提供了一个完成这项工作的过滤器。将以下bean添加到您的配置中@Beanpublic CommonsRequestLoggingFilter requestLoggingFilter() {&nbsp; &nbsp; CommonsRequestLoggingFilter loggingFilter = new CommonsRequestLoggingFilter();&nbsp; &nbsp; loggingFilter.setIncludeClientInfo(true);&nbsp; &nbsp; loggingFilter.setIncludeQueryString(true);&nbsp; &nbsp; loggingFilter.setIncludePayload(true);&nbsp; &nbsp; return loggingFilter;}不要忘记将日志级别更改org.springframework.web.filter.CommonsRequestLoggingFilter为DEBUG。
打开App,查看更多内容
随时随地看视频慕课网APP