Error Logging

Putting an app live can be nerve racking. Who knows what will go wrong once it's released into the wild. So if something breaks you want to know about it with as much detail as possible.

In debug mode, Flask provides some really helpful debugging information with a full stack trace so you know what's going on. But once you turn debugging off, it's an absolute mystery and getting the error messages can mean turning debugging back on which leads to vulnerabilities and general ugliness.

The trouble I was having was when using the errorhandler decorator all the debugging information I wanted (like what the request was, what file caused the error etc) was just being reported as the errorhandler decorated function so really not much use.

After digging around on various forums etc I came across this tutorial which I've adapted for myself.

I created a file called errors.py in my project route which looks like this:

# -*- coding: utf-8 -*-
""" errors.py """
from logging import FileHandler
from logging.handlers import SMTPHandler
from datetime import datetime

from flask import current_app, Markup, render_template, request
from werkzeug.exceptions import default_exceptions, HTTPException


class ErrorHandler():
    def __init__(self, app):
        # add excption handler to all default_exceptions
        for exception in default_exceptions:
            app.register_error_handler(exception, self.error_handler)
        # add error_handler to all Exceptions
        app.register_error_handler(Exception, self.error_handler)
        # add handler
        file_handler = FileHandler('error.log')
        smtp_handler = SMTPHandler(
            mailhost=('smtp.mailgun.org', 587),
            fromaddr='from@example.com',
            toaddrs=['to@example.com'],
            subject='AFL Tipster Error',
            credentials=(
                'postmaster@example.com',
                'password'
            )
        )
        app.logger.addHandler(file_handler)
        app.logger.addHandler(smtp_handler)

    @staticmethod
    def error_handler(error):
        msg = "{}\n{} - Request resulted in {}\nPath: {}\n{}".format(
            "="*50, datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
            error, request.path, "="*50)
        current_app.logger.warning(msg, exc_info=error)

        if isinstance(error, HTTPException):
            description = error.get_description(request.environ)
            code = error.code
            name = error.name
        else:
            description = ("We encountered an error "
                           "while trying to fulfill your request")
            code = 500
            name = 'Internal Server Error'

        templates = ['errors/{}.html'.format(code), 'errors/generic.html']
        return render_template(templates,
                               code=code,
                               name=name,
                               description=Markup(description),
                               error=error)

My new error handler is then initialised in my app's __init__.py file the way most other modules are initialised:

from .errors import ErrorHandler
ErrorHandler(app)

So how does it work? The error_handler static method there which is essentially what logs the errors.

It takes the error as an argument, creates a message which includes a timestamp and the request path as well as the error message itself and then logs that message as a warning in Flask's logging mechanism (which is just an extension of Python's).

I think the important bit that I was missing all along was adding exc_info=error to the logging. Without it, the full stack trace doesn't display.

This static method also renders the appropriate template depending on what type of HTTP error response is raised with some additional parameters passed to the template in case you want to provide additional information to the user.

The __init__ method then loops over all the default exceptions in Flask and and register's our new static method as a handler. That means that it will catch 4xx errors as well as 5xx.

I then specify both a FileHandler and SMTPHandler so that the full stack trace is logged and I get an email every time there is an error.