How to Debug Errors in a Flask App With Werkzeug and Sentry
Now that we have a functional Flask application and know how to build out features, we want to deploy our application to production. We saw a brief example of deploying our simple stock ticker application in Chapter 1, but our application has become a bit more complex with a database, static assets, and configuration secrets. In this section, we will work through the steps to make Yumroad production ready, maintainable, and actually deploy it.
As much as we would like it to be the case (and despite all of the testing we do), our code will likely have some errors. In many large software products, engineers often spend more time fixing (or pro-actively preventing) bugs than actually building new features, so it's important to plan ahead for the developer experience if you want to create a maintainable Flask app.
To serve a production-grade use case it's important to make sure users understand that there was an error and store enough context for the errors so that you can find the root cause and fix it.
Using the Werkzeug debugger
In our local environment when we run into a bug as we use the site, we see the exception pop up in the browser and in our terminal and can try to fix it. Here is an image of the traceback from Flask (which even includes a handy debugger where we can try executing code courtesy of the Werkzeug library).
To use the built in debugger in Werkzeug, we need to first enter in the debugger pin when we first launch the Flask server in our terminal. This is for security reasons since otherwise we'd be letting anyone run code on our machine. This is also one of the reasons we tell Flask what kind of environment we are running through
FLASK_ENV, so that we can disable the debugger in production. We wouldn't want just anyone to run arbitrary code on our servers.
$ flask run * Serving Flask app "yumroad:create_app" (lazy loading) * Environment: development * Debug mode: on * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit) * Restarting with stat * Debugger is active! * Debugger PIN: 234-127-656
Once we hit the debugger pin, we can click on the debugger icon to open a session using the environment of that function up to that point in the code.
Error Monitoring in Production
These tools in our development environment help us quickly figure out what the context for errors are and fix it. In a production environment, you face a lot more challenges in trying to fix issues. For one thing, by default you wouldn't even know that error occurred or the context around how it was created unless a user reaches out to you and tells you. Then in order to pull up the traceback of the exception or error, you'd have to somehow connect to a production server and trawl through logs (if you were keeping them) to find what exactly happened. Once you find a fix and re-deploy it's hard to replicate since you don't know the exact data that a user provided to cause that error.
To solve this lack of visibility, developers use additional software specifically designed to monitor and track errors in production environments. There are a variety of tools that developers use, like Sentry, Rollbar, New Relic, Data Dog, among many more. Armin Ronacher, the creator of Flask and contributor to the ecosystem, actually works at Sentry and was their first engineering hire. Sentry is separately also an open source project and the core web service is written in Python. It seems fitting to use Sentry here, but it's also been my error tracking system of choice for the past seven years thanks to it's excellent UI and features that make it easy to resolve errors. While we'll focus on using Sentry here, the concepts & implementation for many of the other services I mention are similar.
You can register for a free account at Sentry.io or host your instance using the open source Sentry repository. Once you register and create a project, you'll be provided with a configuration key called a DSN and setup instructions.
To get this configured, we will need to install the
sentry Python package, but we'll also want to exclude specific Flask extension which we we can get by adding
sentry[flask] to our
requirements.txt file and then running
pip install -r requirement.txt.
Then we need to specify our configuration key, which Sentry calls a
DSN in our
config.py file. For now to test the integration, we'll add it to the
BaseConfig but it's often only added to the production config.
class BaseConfig: SENTRY_DSN = os.getenv('SENTRY_DSN')
yumroad/__init__.py, we will initialize the SDK and tell it to observe both Flask and SQLAlchemy by specifying those as
integrations for Sentry.
import sentry_sdk from sentry_sdk.integrations.flask import FlaskIntegration from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration def create_app(environment_name='dev'): ... if app.config.get("SENTRY_DSN"): sentry_sdk.init( dsn=app.config["SENTRY_DSN"], integrations=[FlaskIntegration(), SqlalchemyIntegration()] )
Unlike other libraries, we don't need to need to create an extension object as the Sentry Python integration is not a Flask extension. Sentry (through
FlaskIntegration() hooks into different parts of Flask by itself using a feature of Flask called
To test how this works, first add a route to your landing blueprint (
yumroad/blueprints/landing.py) that will always raise an exception.
@landing_bp.route('/error') def error(): return 1/0
Then add your
SENTRY_DSN to your environment (
export SENTRY_DSN='...' in your terminal) and start a development server with
http://localhost:5000/error, should give you a
ZeroDivisionError error & backtrace. Now if you refresh your project in Sentry, you'll see the even show up in your issue log.
Within the specific issue log, you'll see details like which route the error occurred in, what kind of exception, the backtrace, how often this error is occuring and more.