This video is available to students only

How to Debug Errors in a Flask App With Werkzeug and Sentry

Project Source Code

Get the project source code below, and follow along with the lesson material.

Download Project Source Code

To set up the project on your local machine, please follow the directions provided in the README.md file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.

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.

Errors

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).

An example exception when we set the price of a product too low
An example exception when we set the price of a product too low

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
Pin Prompt
Pin Prompt

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.

Debugger
Debugger

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.

Sentry Setup
Sentry Setup

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')

Then in 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 signals).

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 flask run.

Loading 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.

Issue Log
Issue Log

This lesson preview is part of the Fullstack Flask: Build a Complete SaaS App with Flask course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.

Unlock This Course

Get unlimited access to Fullstack Flask: Build a Complete SaaS App with Flask, plus 70+ \newline books, guides and courses with the \newline Pro subscription.

Thumbnail for the \newline course Fullstack Flask: Build a Complete SaaS App with Flask