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.