Error Handling and Logging
Introduction
Users hit wrong URLs. Code throws exceptions. Production must return friendly error pages without exposing stack traces, while developers need logs to diagnose issues. This chapter covers @app.errorhandler, debug vs production behavior, and configuring Python logging for Flask apps.
Prerequisites
HTTP Error Handlers
templates/errors/404.html:
{% extends "base.html" %}
{% block content %}
<h1>404 — Page not found</h1>
<p>The page you requested does not exist.</p>
<a href="{{ url_for('index') }}">Go home</a>
{% endblock %}Code explanation:
errorhandlerregisters a view for a status code or exception class- Return status tuple so the HTTP code matches the error
abort in Views
from flask import Flask, abort
app = Flask(__name__)
@app.route("/posts/<int:post_id>")
def show_post(post_id):
post = None
if post is None:
abort(404)
return render_template("post.html", post=post)Custom message:
abort(403, description="You cannot edit this post.")Read in handler:
@app.errorhandler(403)
def forbidden(e):
return render_template("errors/403.html", message=e.description), 403Debug Mode vs Production
Development (DEBUG=True):
- Interactive Werkzeug debugger in browser on 500 errors
- Auto reload on code changes
Warning
Never Enable Debug in Production
The debugger allows arbitrary Python execution on the server—critical security risk if exposed publicly.
Production:
app.config["DEBUG"] = False
app.config["PROPAGATE_EXCEPTIONS"] = FalseUse Gunicorn with debug off—see deployment chapter.
Flask app.logger
Levels: debug, info, warning, error, exception (includes traceback).
Configure Logging
Code explanation:
RotatingFileHandlerprevents log files from filling the disk- Gunicorn also logs to stdout—collect with systemd or Docker
Request Context in Logs
Structured logging (JSON lines) helps log aggregators—concept for larger teams.
Handle Specific Exceptions
class AppError(Exception):
def __init__(self, message, status_code=400):
super().__init__(message)
self.message = message
self.status_code = status_code
@app.errorhandler(AppError)
def handle_app_error(e):
return jsonify({"error": e.message}), e.status_codeRaise in business logic:
raise AppError("Email already registered", 409)FAQ
Custom 404 still shows Werkzeug page?
Ensure handler is registered before app.run and DEBUG template exists.
Log duplicate lines?
Flask/Werkzeug may add handlers—check app.logger.handlers; avoid duplicate basicConfig.
500 page in dev shows debugger?
Expected with DEBUG=True—disable debug to test custom 500 template.
Log sensitive data?
Never log passwords, tokens, or full credit card numbers.
Gunicorn logs vs app logs?
Gunicorn logs worker crashes; app.logger logs application events—use both.
JSON API always return JSON errors?
Branch on request.path.startswith("/api/") or request.accept_mimes.accept_json.