Convenient stacktrace formatter for Django logging

When I debug Open edX, with multiple concurrently running applications, I often find myself missing the Django default exception stacktrace reporter:

This stacktrace is extremely convenient: it displays the stacktrace in a very readable format, along with all local variable values and tons of additional information, such as settings. Also, it’s neatly packaged in a single static html page.

However, this page is only displayed when a synchronous http requests results in a 500 error. I don’t have access to all that information when I troubleshoot, for instance:

  1. Calls between separate applications
  2. Ajax requests
  3. Asynchronous Celery tasks

It occurred to me that I could create a single logging formatter that would generate that same html code. The implementation is really simple, and consists of a simple modification of the LOGGING setting:

import logging
class StacktraceFormatter(logging.Formatter):
    def format(self, record):
        """
        Html-formatted stacktraces

        (heavily inspired by django.utils.log.AdminEmailHandler)
        """
        try:
            request = record.request
        except Exception:
            request = None
        if record.exc_info:
            exc_info = record.exc_info
        else:
            exc_info = (None, record.getMessage(), None)

        from django.views.debug import ExceptionReporter
        reporter = ExceptionReporter(request, is_email=False, *exc_info)
        return reporter.get_traceback_html()

LOGGING["formatters"]["stacktrace"] = {
    "class": "lms.envs.devstack.StacktraceFormatter"
}
# Configure a handler to use this formatter
LOGGING["handlers"]["stacktrace"] = {
    "level": "ERROR",
    "class": "logging.handlers.RotatingFileHandler",
    # Rotate very frequently
    "maxBytes": 512,
    # Keep just a few error stacktraces
    "backupCount": 10,
    # Files will be saved as stacktrace.html, stacktrace.html.1, stacktrace.html.2, ...
    "filename": os.path.join(LOG_DIR, "stacktrace.html"),
    "formatter": "stacktrace",
}
# Configure the root logger to use this handler
LOGGING["loggers"][""]["handlers"].append("stacktrace")

With this simple configuration change, I get all stacktraces stored as stacktrace.html.* files in my LOG_DIR and I can explore them with my web browser. This is basically the poor man’s Sentry. I find this extremely convenient, and I’m surprised that I did not find any other Python/Django project that implemented the same feature. So I wanted to ask you fellow Open edX developers if you have ever seen something similar? In particular I’m curious to hear about you @nedbat :wink:

5 Likes

I haven’t seen something like this before, but it is very clever, and I might use it elsewhere!