I have a Spring Boot application that uses Sentry for exception tracking and I'm getting some errors that look like this:
ClientAbortExceptionorg.apache.catalina.connector.OutputBuffer in realWriteBytes
errorjava.io.IOException: Broken pipe
My understanding is that it's just a networking error and thus I should generally ignore them. What I want to do is report all other IOExceptions
and log broken pipes to Librato so I can keep an eye on how many I'm getting (a spike might mean there's an issue with the client, which is also developed by me in Java):
I came up with this:
@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;
}
}
Is that an acceptable way to deal with this problem?
I have Sentry configured following the documentation this way:
@Configuration
public class FactoryBeanAppConfig {
@Bean
public HandlerExceptionResolver sentryExceptionResolver() {
return new SentryExceptionResolver();
}
@Bean
public ServletContextInitializer sentryServletContextInitializer() {
return new SentryServletContextInitializer();
}
}
If you look at the class 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;
}
}
By returning Integer.MIN_VALUE
in getOrder
it makes sure that it gets called first. Even though you have set the Priority
to 1
, it won't work. So you want to change your
@Configuration
public class FactoryBeanAppConfig {
@Bean
public HandlerExceptionResolver sentryExceptionResolver() {
return new SentryExceptionResolver();
}
@Bean
public ServletContextInitializer sentryServletContextInitializer() {
return new SentryServletContextInitializer();
}
}
to
@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();
}
}
This will ensure you can have your handler can be run earlier. In your code the loop to get rootCause
is incorrect
while (ex.getCause() != null) {
rootCause = ex.getCause();
}
This is a infinite loop as you have used ex
instead of rootCause
. Even if you correct it, it can still become an infinite loop. When the exception cause returns itself it will be stuck. I have not thoroughly tested it but I believe it should be like below
while (rootCause.getCause() != null && rootCause.getCause() != rootCause) {
rootCause = rootCause.getCause();
}
The is one way of solving your problem. But you need to send the exception yourself to Sentry. So there is another way to handle your requirement
Way 2
In this case you can do the whole logic in your Configuration and change it to below
@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();
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With