Python Flask: catch and handle exceptions in routes using errorhandler
No matter how much your test your code, there might be an occassional exception raised during the execution of one of the routes in your code. You cannot wrap all the code in a huge try-except block, but you can use the built-in error-handling of Flask using the errrohandler hook that we have already seen when we wanted to have a unified look for 404 page not found.
Catching the Exception
@app.errorhandler(Exception) def server_error(err): app.logger.exception(err) return "exception", 500
Alternativelly, using a template:
@app.errorhandler(Exception) def server_error(err): return render_template('crash.html'), 500
Sample application handling the Exception
examples/flask/catch_exception/app.py
from flask import Flask import datetime app = Flask(__name__) @app.errorhandler(Exception) def server_error(err): app.logger.exception(err) return "exception", 500 @app.route("/") def main(): app.logger.info("main route") return "Hello " + str(datetime.datetime.now()) @app.route("/crash") def crash(): app.logger.info("crash route") a = 0 b = 3 / a
Run as:
FLASK_APP=app FLASK_DEBUG=1 flask run
Output on the console for regular pages.
[2020-06-26 10:33:22,903] INFO in app: main route 127.0.0.1 - - [26/Jun/2020 10:33:22] "GET / HTTP/1.1" 200 -
Oputput of the crash
Visiting the http://localhost:5000/crash URL shows a stack trace like this on the command line:
[2020-06-26 10:33:29,633] INFO in app: crash route [2020-06-26 10:33:29,633] ERROR in app: division by zero Traceback (most recent call last): File ".../flask/app.py", line 1950, in full_dispatch_request rv = self.dispatch_request() File ".../flask/app.py", line 1936, in dispatch_request return self.view_functions[rule.endpoint](**req.view_args) File ".../flask/catch_exception/app.py", line 20, in crash b = 3 / a ZeroDivisionError: division by zero 127.0.0.1 - - [26/Jun/2020 10:33:29] "GET /crash HTTP/1.1" 200 -
The user only sees the word "exception".
Of course insted of returning that word, we could have used render_template to render any page. The status code returned by the page will be 500 as indicated by the second value returned.
Catching specific exceptions
In the above examples we use the catch-all Exception class, but you can be more specific about the exceptions and you can return different pages based on the error type. You could do this by checking the type of the err variable, but a probably cleaner way is to set up separate exception handler hooks for each exception type you'd like to deal with:
examples/flask/catch_specific_exception/app.py
from flask import Flask import datetime app = Flask(__name__) @app.errorhandler(Exception) def server_error(err): app.logger.exception(err) return "Some general exception", 500 @app.errorhandler(ZeroDivisionError) def server_error(err): app.logger.exception(err) return "Cannot divide by 0", 500 @app.route("/") def main(): app.logger.info("main route") return "Hello " + str(datetime.datetime.now()) @app.route("/crash") def crash(): app.logger.info("crash route") raise Exception("just crash") @app.route("/calc") def calc(): app.logger.info("calc route") a = 0 b = 3 / a
Logging the Exception
In the above examples we keept using the app.logger.exception(err) expression to log the exception and the stack trace. If you are not interested in the stack-trace you can use the error logging method: app.logger.error(err).
Published on 2020-06-26