为了账号安全,请及时绑定邮箱和手机立即绑定

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

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

万千封印 2019-08-30 15:38:40
我正在用弹簧靴做休息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,但正如我所提到的,我需要在单个位置(和单个日志)处理所有成功和错误请求)。
查看完整描述

3 回答

?
MMMHUHU

TA贡献1834条经验 获得超8个赞

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


但是有了这个要求,你必须访问存储在其中handlerMapping的信息DispatcherServlet。也就是说,您可以覆盖DispatcherServlet以完成请求/响应对的记录。


以下是一个可以进一步增强和满足您需求的想法示例。


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

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


如果出现错误,Spring会自动进行错误处理。因此,BasicErrorController#error显示为请求处理程序。如果要保留原始请求处理程序,则可以在调用spring-webmvc-4.2.5.RELEASE-sources.jar!/org/springframework/web/servlet/DispatcherServlet.java:971之前覆盖此行为#processDispatchResult,以缓存原始处理程序。


查看完整回答
反对 回复 2019-08-30
?
拉莫斯之舞

TA贡献1820条经验 获得超10个赞

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

此外,此应用程序将在何处运行?你会使用PaaS吗?例如,主机提供商Heroku提供请求记录作为其服务的一部分,您无需进行任何编码。


查看完整回答
反对 回复 2019-08-30
?
UYOU

TA贡献1878条经验 获得超4个赞

Spring已经提供了一个完成这项工作的过滤器。将以下bean添加到您的配置中


@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。


查看完整回答
反对 回复 2019-08-30
  • 3 回答
  • 0 关注
  • 1218 浏览

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信