We use Sentry for our error reporting, and we love it. It is simple, gives options for a lot of different use cases, and can easily be integrated with Django, over which our backend is built. We recently fixed (or ignored :P) an issue we had for a long time. This article is about how we did it.
Because of a play between Django-Tastypie and Celery-Haystack, our celery workers ended up throwing a lot (5 million in 2 years) of harmless
ValueError exceptions. Due to other priorities and a large amount of effort required, we couldn’t get down to fixing the source of these exceptions. But soon we started reaching our rate-limits for errors on Sentry. We needed a way to make sentry ignore these particular errors.
If it was a logged error (like
logger.error), we could simply configure Django’s logging framework appropriately for sentry, but it was an unhandled exception so that would not work.
There are some options for making sentry exclude particular errors:
ignore_exceptions: This is helpful if you want a particular exception to be ignored, and is a more suitable use-case for ignoring custom Exceptions. But we can’t ignore
ValueErrorcompletely since we stand a chance to ignore other genuine Exceptions that must be brought to our notice.
exclude_paths: This should have worked (according to the docs) and I should have just needed to append
celery_haystack.tasks, but that didn’t turn out to be the case. I couldn’t make it work in my testing (we have
Getting no headway there, and in the interest of time (we didn’t want to spend more time than necessary), we decided to dig into some code and look for a good way to achieve this by overriding Sentry Client.
Looking into the code revealed that
skip_error_for_logging is the perfect method to override for our use. We are taking a list of prefixes that can be defined in Django
settings and testing them against the exception message. If the exception message has one of the specified prefixes, we ignore the error.
Here is our solution:
|from django.conf import settings|
|from raven.contrib.django.client import DjangoClient|
|ignore_exc_msg_prefixes = getattr(settings, 'CUSTOM_SENTRY_CLIENT_IGNORE_EXCEPTION_MESSAGE_PREFIXES', )|
|Overrides DjangoClient's `skip_error_for_logging` method.CUSTOM_SENTRY_CLIENT_IGNORE_EXCEPTION_MESSAGE_PREFIXES|
|Checks raised error message for prefixes defined in settings.CUSTOM_SENTRY_CLIENT_IGNORE_EXCEPTION_MESSAGE_PREFIXES|
|If the error message has any of the given prefix, skips logging the exception|
|def skip_error_for_logging(self, exc_info):|
|skip_error = super(IgnoreExceptionsDjangoClient, self).skip_error_for_logging(exc_info)|
|if not skip_error and ignore_exc_msg_prefixes:|
|error_msg = exc_info.message|
|if isinstance(error_msg, basestring):|
|for prefix in ignore_exc_msg_prefixes:|
|skip_error = True|
|SENTRY_CLIENT = 'path.to.sentry_client.IgnoreExceptionsDjangoClient'|
|CUSTOM_SENTRY_CLIENT_IGNORE_EXCEPTION_MESSAGE_PREFIXES = [|
|"Couldn't load object" # raised by celery_haystack task|
This provides a lot of control over what exception we want to exclude. And can be extended to check for Exception
message together, use regexes, or any other advanced handling we like.
Result? Our hourly error rate instantly came down. Here is one of the heart-warming screenshots :)
The green dots are when the deployment took place
I still have a feeling that
exclude_paths should have worked, but we went ahead with the solution we could figure out the fastest. If someone knows a better way, please let us know :)
(Originally posted on my Medium account)