Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Send production errors to slack instead of email

I wanted to make my Django app send debugging information to slack instead of by email, which is the default.

  • Disabling email is simple. Just don't put any emails in ADMINS setting
  • Sending information to slack is easy. Just add an incoming webhook

Now, where should i create the logic that sends the messge? Middleware seems to be a pretty good idea. I would do something like

class ExceptionMiddleware:
    def process_exception(self, request, exception):
        pretty_debugging_message = ...
        requests.post("https://my-slack-url/", {...})

The middleware would just return None so as to not interfere with the rest of the system; after all, the emailing has already been disabled.

So my question is: How do I get all the debugging info goodness that django collects? I can do something like

import sys, traceback
pretty_debugging_message = '\n'.join(
    traceback.format_exception(*sys.exc_info())
)

But this only provides the traceback. What about all of the locals, the session, the clients IP etc.?

Reading through https://docs.djangoproject.com/en/1.8/howto/error-reporting/, I get the idea that all of that information is not collected until after middleware is handled, that is, once everything is tried, and an error has not been handled, then Django runs its ErrorReporter and logs the information. Could I intervene with that process somehow and make it send the info to slack? Would that be better?

Update

My solution:

class SlackHandler(AdminEmailHandler):
    def emit(self, record):
        try:
            request = record.request
            subject = '%s (%s IP): %s' % (
                record.levelname,
                ('internal' if request.META.get('REMOTE_ADDR') in settings.INTERNAL_IPS
                 else 'EXTERNAL'),
                record.getMessage()
            )
            filter = get_exception_reporter_filter(request)
            request_repr = '\n{0}'.format(filter.get_request_repr(request))
        except Exception:
            subject = '%s: %s' % (
                record.levelname,
                record.getMessage()
            )
            request = None
            request_repr = "unavailable"
        subject = self.format_subject(subject)

        if record.exc_info:
            exc_info = record.exc_info
        else:
            exc_info = (None, record.getMessage(), None)

        message = "%s\n\nRequest repr(): %s" % (self.format(record), request_repr)
        reporter = ExceptionReporter(request, is_email=True, *exc_info)
        html_message = reporter.get_traceback_html() if self.include_html else None

        requests.post(settings.SLACK_WEBHOOK_URL, json={
            "fallback": message,
            "pretext": "An error occured",
            "color": "#ef2a2a",
            "fields": [
                {
                    "title": "Error",
                    "value": message,
                    "short": False
                }
            ]
        })

In settings.py:

LOGGING = {
    'version': 1,
    'disable_existing_loggers': False,
    'filters': {
        'require_debug_false': {
            '()': 'django.utils.log.RequireDebugFalse'
        }
    },
    'handlers': {
        'slack': {
            'level': 'ERROR',
            'filters': ['require_debug_false'],
            'class': 'myapp.myapp.SlackHandler'
        }
        # 'mail_admins': {
        #     'level': 'ERROR',
        #     'filters': ['require_debug_false'],
        #     'class': 'django.utils.log.AdminEmailHandler'
        # }
    },
    'loggers': {
        'django.request': {
            'handlers': ['slack'],
            'level': 'ERROR',
            'propagate': True,
        },
    }
}
like image 708
Eldamir Avatar asked Apr 28 '15 08:04

Eldamir


1 Answers

I would advise you to create a custom logging handler. You can have a look at the AdminEmailHandler implementation and make your own or, even simpler, if that suits your needs, subclass it and only override the send_mail method.

import requests

from django.utils.log import AdminEmailHandler


class SlackHandler(AdminEmailHandler):
    def send_mail(self, subject, message, *args, **kwargs):
        html_message = kwargs.get('html_message')
        requests.post("https://my-slack-url/", {...})

You then need to configure the LOGGING setting to use your new handler instead of AdminEmailHandler. Here is Django's default logging configuration.

Example:

'handlers': {
    'slack': {
        'level': 'ERROR',
        'filters': ['require_debug_false'],
        'class': 'import.path.to.SlackHandler'
    }
},
# ...
'loggers': {
    'django.request': {
        'handlers': ['slack'],
        'level': 'ERROR',
        'propagate': False,
    },
    # ...
}
like image 94
aumo Avatar answered Sep 21 '22 15:09

aumo