Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Making the stack levels in Django HTML email reports collapsable

Django has an awesome debug page that shows up whenever there's an exception in the code. That page shows all levels in the stack compactly, and you can expand any level you're interested in. It shows only in debug mode.

Django also has an awesome feature for sending email reports when errors are encountered on the production server by actual users. These reports have a stacktrace with much less information than the styled debug page.

There's an awesome optional setting 'include_html': True that makes the email include all the information of the debug page, which is really useful. The problem with this setting is that the HTML comes apparently unstyled, so all those levels of the stack are expanded to show all the data they contain.

This results in such a long email that GMail usually can't even display it without sending you to a dedicated view. But the real problem is that it's too big to navigate in and find the stack level you want.

What I want: I want Django to send that detailed stacktrace, but I want the levels of the stack to be collapsable just like in the debug page. How can I do that?

(And no, I don't want to use Sentry.)

like image 705
Ram Rachum Avatar asked May 30 '14 14:05

Ram Rachum


1 Answers

We use a middleware to process any exceptions that happen in production. The basic idea is save the debug html to a location on the file system that can be served via a password protected view and then send a link to the generated view to whoever needs it.

Here is a basic implementation:

from django.conf import settings
from django.core.mail import send_mail
from django.views import debug
import traceback
import hashlib
import sys

class ExceptionMiddleware(object):

    def process_exception(self, request, exception):
        if isinstance(exception, Http404):
            return
        traceback = traceback.format_exc()
        traceback_hash = hashlib.md5(traceback).hexdigest()
        traceback_name = '%s.html' % traceback_hash
        traceback_path = os.path.join(settings.EXCEPTIONS_DIR, traceback_name)
        reporter = debug.ExceptionReporter(request, *sys.exc_info())
        with open(traceback_path, 'w') as f:
            f.write(reporter.get_traceback_html().encode("utf-8"))
        send_mail(
            'Error at %s' % request.path,
            request.build_absolute_uri(reverse('exception', args=(traceback_name, ))),
            FROM_EMAIL,
            TO_EMAIL,
        )

And the view

from django.conf import settings
from django.http import HttpResponse

def exception(request, traceback_name):
    traceback_path = os.path.join(settings.EXCEPTIONS_DIR, traceback_name)
    with open(traceback_path, 'r') as f:
        response = HttpResponse(f.read())
    return response

The full middleware is tailored to our needs but the basics are there. You should probably password protect the view somehow. Unless you return a response Django's own error handling will kick in and still send you an email but I'll leave that to you.

like image 156
Iain Shelvington Avatar answered Sep 22 '22 15:09

Iain Shelvington