Избегайте сообщения об ошибках Broken Pipe для Sentry в приложении Spring Boot

У меня есть приложение 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;
    }
}

Это приемлемый способ справиться с этой проблемой?

После этого я настроил Sentry следующим образом:

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

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

Ответы

Ответ 1

Если вы посмотрите на класс 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;
    }
}

Возвращая Integer.MIN_VALUE в getOrder он гарантирует, что он будет вызван первым. Даже если вы установили Priority в 1, это не сработает. Поэтому вы хотите изменить свою

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

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

в

@Configuration
public class FactoryBeanAppConfig {
    @Bean
    public HandlerExceptionResolver sentryExceptionResolver() {
        return new SentryExceptionResolver() {
                @Override
                public int getOrder() {
                    // ensure we can get some resolver earlier than this
                    return 10;
                }
         };
    }

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

Это гарантирует, что ваш обработчик может быть запущен раньше. В вашем коде цикл для rootCause неверен

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

Это бесконечный цикл, поскольку вы использовали ex вместо rootCause. Даже если вы его исправите, он все равно может стать бесконечным циклом. Когда причина исключения вернется сама, она застрянет. Я не проверил его полностью, но считаю, что это должно быть как ниже

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

Это один из способов решения вашей проблемы. Но вам нужно отправить исключение самостоятельно в Sentry. Таким образом, есть еще один способ справиться с вашим требованием

Путь 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();
    }
}