Java 避免在Spring引导应用程序中向Sentry报告断管错误

Java 避免在Spring引导应用程序中向Sentry报告断管错误,java,spring,spring-boot,sentry,Java,Spring,Spring Boot,Sentry,我有一个Spring Boot应用程序,它使用Sentry进行异常跟踪,我发现一些错误如下所示: ClientAbortExceptionorg.apache.catalina.connector.OutputBuffer in realWriteBytes errorjava.io.IOException: Broken pipe 我的理解是,这只是一个网络错误,因此我通常应该忽略它们。我想做的是报告所有其他IOExceptions,并将损坏的管道记录到Librato,这样我就可以关注我得到

我有一个Spring Boot应用程序,它使用Sentry进行异常跟踪,我发现一些错误如下所示:

ClientAbortExceptionorg.apache.catalina.connector.OutputBuffer in realWriteBytes
errorjava.io.IOException: Broken pipe
我的理解是,这只是一个网络错误,因此我通常应该忽略它们。我想做的是报告所有其他
IOExceptions
,并将损坏的管道记录到Librato,这样我就可以关注我得到的管道数量(峰值可能意味着客户端出现问题,这也是我用Java开发的):

我想到了这个:

@ControllerAdvice
@Priority(1)
@Order(1)
public class RestExceptionHandler {
    @ExceptionHandler(ClientAbortException.class)
    @ResponseStatus(HttpStatus.SERVICE_UNAVAILABLE)
    public ResponseEntity<?> handleClientAbortException(ClientAbortException ex, HttpServletRequest request) {
        Throwable rootCause = ex;
        while (ex.getCause() != null) {
            rootCause = ex.getCause();
        }
        if (rootCause.getMessage().contains("Broken pipe")) {
            logger.info("count#broken_pipe=1");
        } else {
            Sentry.getStoredClient().sendException(ex);
        }
        return null;
    }
}

如果查看类
SentryExceptionResolver

public class SentryExceptionResolver implements HandlerExceptionResolver, Ordered {
    @Override
    public ModelAndView resolveException(HttpServletRequest request,
                                         HttpServletResponse response,
                                         Object handler,
                                         Exception ex) {

        Sentry.capture(ex);

        // null = run other HandlerExceptionResolvers to actually handle the exception
        return null;
    }

    @Override
    public int getOrder() {
        // ensure this resolver runs first so that all exceptions are reported
        return Integer.MIN_VALUE;
    }
}
通过在
getOrder
中返回
Integer.MIN_值
,它确保首先调用它。即使您已将
优先级设置为
1
,它也不会工作。所以你想改变你的想法

@Configuration
public class FactoryBeanAppConfig {
    @Bean
    public HandlerExceptionResolver sentryExceptionResolver() {
        return new SentryExceptionResolver();
    }

    @Bean
    public ServletContextInitializer sentryServletContextInitializer() {
        return new SentryServletContextInitializer();
    }
}

这将确保您的处理程序可以更早地运行。在代码中,获取根源的循环不正确

while (ex.getCause() != null) {
    rootCause = ex.getCause();
}
这是一个无限循环,因为您使用了
ex
而不是
rootCause
。即使你纠正了它,它仍然可以成为一个无限循环。当异常原因返回自身时,它将被卡住。我还没有完全测试它,但我相信它应该是如下所示

while (rootCause.getCause() != null && rootCause.getCause() != rootCause) {
    rootCause = rootCause.getCause();
}
这是解决你问题的一种方法。但是你需要亲自把异常情况送到哨兵那里。因此,有另一种方法来处理您的需求

方式2

在这种情况下,您可以在配置中执行整个逻辑,并将其更改为以下内容

@Configuration
public class FactoryBeanAppConfig {
    @Bean
    public HandlerExceptionResolver sentryExceptionResolver() {
        return new SentryExceptionResolver() {
            @Override
            public ModelAndView resolveException(HttpServletRequest request,
                    HttpServletResponse response,
                    Object handler,
                    Exception ex) {
                Throwable rootCause = ex;

                while (rootCause .getCause() != null && rootCause.getCause() != rootCause) {
                    rootCause = rootCause.getCause();
                }

                if (!rootCause.getMessage().contains("Broken pipe")) {
                    super.resolveException(request, response, handler, ex);
                }
                return null;
            }   

        };
    }

    @Bean
    public ServletContextInitializer sentryServletContextInitializer() {
        return new SentryServletContextInitializer();
    }
}

我认为提供的代码示例没有问题,但Sentry在应用程序的其余部分是如何配置的?如果其他地方正在捕获异常并将其发送给哨兵,那么这并不能阻止这种情况的发生。还是你在使用Sentry日志集成,只发送错误或以上的内容?@Brett:谢谢你的评论。我在问题中添加了这一信息。我认为通过返回
null
您告诉Spring运行其他处理程序,这似乎会运行Sentry解析器,而Sentry解析器会将其发送给Sentry?您可能需要返回有效的
响应属性
?至少这是我在实现Sentry解析器时发现的:@Pablo,我假设这不起作用,并且仍然将请求发送给Sentry?@TarunLalwani:我不知道,因为这不容易测试。我正在检查这段代码,为了弄清异常,它不应该执行
rootCause=rootCause.getCause()。否则,它只能执行一步,因为
ex
永远不会更改。@pupeno,是的,你是对的。当我测试它时,它只有一个级别的深度异常,所以没有捕捉到这个。它确实应该是
rootCause=rootCause.getCause()酷!我更正了答案。令人惊讶的是,我复制了代码,幸运的是我只需要处理两个级别(直到现在),所以我非常困惑。谢谢更新。很好,错误发生了,您可以注意到问题:-)嗨,我想知道我们是应该忽略“断管”错误还是应该报告它?因为默认情况下,Sentry和Elastic APM都将此报告为错误。我个人认为我们应该忽略它,但为什么这两个图书馆报告它呢?
while (rootCause.getCause() != null && rootCause.getCause() != rootCause) {
    rootCause = rootCause.getCause();
}
@Configuration
public class FactoryBeanAppConfig {
    @Bean
    public HandlerExceptionResolver sentryExceptionResolver() {
        return new SentryExceptionResolver() {
            @Override
            public ModelAndView resolveException(HttpServletRequest request,
                    HttpServletResponse response,
                    Object handler,
                    Exception ex) {
                Throwable rootCause = ex;

                while (rootCause .getCause() != null && rootCause.getCause() != rootCause) {
                    rootCause = rootCause.getCause();
                }

                if (!rootCause.getMessage().contains("Broken pipe")) {
                    super.resolveException(request, response, handler, ex);
                }
                return null;
            }   

        };
    }

    @Bean
    public ServletContextInitializer sentryServletContextInitializer() {
        return new SentryServletContextInitializer();
    }
}