I have a Flask server.
Whenever the code inside my handler throws an exception, Flask catches it, and returns an HTML page to the client with a 5XX error.
The problem is that I don't notice this. I just got an email from someone using my API saying that they were getting 504 errors, and I didn't know about it until they told me.
In other non-Flask parts of my application I wrote a custom decorator to catch all exceptions, send an email to me, then re-throw. I would like something similar for my Flask app.
I want to find a way to have Flask call a function of mine every time my handler code throws an exception, before it returns a response to the client. I do not wish to modify the response that gets sent to the client. I don't want to change how Flask handles errors, or how it catches them. I just want some way of being notified, and then Flask can continue doing the default error handling behavior.
I suppose I could put a decorator over every single route handler to catch and rethrow exceptions before Flask sees them, but that's messy. I just know I'll forget one of them, especially when I add new ones in the future.
MWE
A buggy application:
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello():
assert False, "buggy code here"
return "hello"
def error_handler(exc_type, exc_val, exc_tb):
send_email(exc_type, exc_val, exc_tb)
# This is the part I don't know
# I want something along the lines of:
app.config['ERROR_HOOK'] = error_handler
from flask import Flask
app = Flask(__name__)
app.debug = False
app.config['PROPAGATE_EXCEPTIONS'] = True
#app.errorhandler(Exception)
def all_exception_handler(error):
print(str(error))
#app.errorhandler(404)
def page_not_found(error):
return 'This page does not exist', 404
you can define a function for each specific error you want to catch #app.my_custom_errorhandler(code_or_exception)
The argument to your error handler function will be an Exception.
When declaring a function with before_request Flask is swallowing HTTPExceptions and not propagating them:
from flask import Flask
from werkzeug.exceptions import HTTPException
app = Flask(__name__)
#app.before_request
def f():
raise HTTPException("This is not propagated")
#app.route('/')
def test():
return 'This is a view'
if __name__ == '__main__':
app.run()
The console shows no Exception:
$ python flask_test1.py
* Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [02/Jun/2016 15:01:31] "GET / HTTP/1.1" 200 -
The Browser shows:
Unknown Error
This is not propagated
Is there a way to force it ? A setting I need to specify, or should I interface Werkzeug HTTPExceptions and raise custom made Exceptions ?
The likely reason isn't that you aren't supposed to raise HTTPException itself. HTTPException is itself supposed to be a base class for other exceptions, such as BadRequest or NotFound.
Since HTTPException has no HTTP code to return, no such code is logged or sent to the user. If you raise NotFound, BadRequest, or any of the subclasses of HTTPException, those work fine, at least in Flask 0.11:
from werkzeug.exceptions import BadRequest
#app.before_request
def f():
raise BadRequest("This is definitely propagated")
The documentation for after_request says "As of Flask 0.7 this function might not be executed at the end of the request in case an unhandled exception occurred." Is there a way to change this so after_request functions are called even for unhandled exceptions, for example to log the traceback?
Use teardown_request instead.
Register a function to be run at the end of each request, regardless of whether there was an exception or not.
These functions are not allowed to modify the request, and their return values are ignored. If an exception occurred while processing the request, it gets passed to each teardown_request function.
from flask import Flask
app = Flask(__name__)
# unhandled teardown won't happen while in debug mode
# app.debug = True
# set this if you need the full traceback, not just the exception
# app.config['PROPAGATE_EXCEPTIONS'] = True
#app.route('/')
def index():
print(test)
#app.teardown_request
def log_unhandled(e):
if e is not None:
print(repr(e))
# app.logger.exception(e) # only works with PROPAGATE_EXCEPTIONS
app.run('localhost')
Note that by the time teardown_request is called, the traceback has already fallen out of scope; only the exception is available. You can change this by setting PROPAGATE_EXCEPTIONS = True, although this may have performance issues. Given that the traceback is logged by Flask already, it may be easier to configure logging rather than trying to log it yourself.
I have a Flask application that works well and produces an occasional error, which is visible when it is running with debug=True:
if __name__ == '__main__':
app.run(debug=True)
I get useful error messages such as:
Traceback (most recent call last):
File "./main.py", line 871, in index_route
KeyError: 'stateIIIII'
I would like to get error messages like these saved to a file when I run the application in production (using Lighttpd + fastcgi).
After looking at various StackOverflow questions (http://flask.pocoo.org/docs/errorhandling/, http://docs.python.org/2/library/logging.html, etc.); the Flask mailing list; and a few blogs, it seems there is no easy way just to send all the great error messages to a file - I need to use the Python logging module to customise things. So I came up with the following code.
At the top of my application file I have various imports followed by:
app = Flask(__name__)
if app.debug is not True:
import logging
from logging.handlers import RotatingFileHandler
file_handler = RotatingFileHandler('python.log', maxBytes=1024 * 1024 * 100, backupCount=20)
file_handler.setLevel(logging.ERROR)
app.logger.setLevel(logging.ERROR)
app.logger.addHandler(file_handler)
I have then put the code for each route in a try/except statement and use traceback to work out which line the error came from and print a nice error message:
def some_route():
try:
# code for route in here (including a return statement)
except:
exc_type, exc_value, exc_traceback = sys.exc_info()
app.logger.error(traceback.print_exception(exc_type, exc_value, exc_traceback, limit=2))
return render_template('error.html')
And then right at the end of the file I remove the debug=True statement. Though I don't think I need to do that as the application is being run by a fastcgi server(?) when it is run in production. The last two lines of my application code look like this:
if __name__ == '__main__':
app.run()
I am struggling to get this working. I think the best I have managed is to get a single error log message to be saved in the file using (app.logger.error('test message') ), but it only prints that one message. An attempt to log another error directly after that one is simply ignored.
I don't know why it's not working but I can tell how am doing this.
First of all, you don't need to set the level of app.logger. So remove this line app.logger.setLevel().
You want to save exception and return error page for every view. It is a lot of work to write this code everywhere. Flask provides a method to do this. Define an errorhandler method like this.
#app.errorhandler(500)
def internal_error(exception):
app.logger.error(exception)
return render_template('500.html'), 500
Whenever a view raises an exception, this method will be called and passed the exception as argument. Python logging provides exception method that is used to save full traceback of the exception.
Since this handles all exception, you don't even need to put code in try/except block. Though, if you want to do something before calling the errorhandler(for e.g. rollback session or transaction) then do this:
try:
#code
except:
#code
raise
If you would like the date and time added for each entry in your log file, the following code can be used (in place of the similar code featured in the question).
if app.debug is not True:
import logging
from logging.handlers import RotatingFileHandler
file_handler = RotatingFileHandler('python.log', maxBytes=1024 * 1024 * 100, backupCount=20)
file_handler.setLevel(logging.ERROR)
formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
file_handler.setFormatter(formatter)
app.logger.addHandler(file_handler)
For those who read this later on.
I think it is better idea to push more useful info into error messages. URL, client IP, user-agent etc. Flask logs exceptions internally (in app.debug==False mode) with Flask.log_exception function. So, instead of logging things manually in #app.errorhandler I do something like this:
class MoarFlask(Flask):
def log_exception(self, exc_info):
"""...description omitted..."""
self.logger.error(
"""
Request: {method} {path}
IP: {ip}
User: {user}
Agent: {agent_platform} | {agent_browser} {agent_browser_version}
Raw Agent: {agent}
""".format(
method = request.method,
path = request.path,
ip = request.remote_addr,
agent_platform = request.user_agent.platform,
agent_browser = request.user_agent.browser,
agent_browser_version = request.user_agent.version,
agent = request.user_agent.string,
user=user
), exc_info=exc_info
)
Then, at configuration time, bind FileHandler to app.logger and go on.
I don't use StreamHandler cause many servers (e.g. uWSGI) like to pollute it
with their own proprietary-wordy-useless-not-turnable-off messages.
Don't be afraid of extending Flask. You'll be forced to do it sooner or later ;)
This is what I generally do when a service requires logging. I'm not a specialist on the subject, but these observations are something to take in consideration, in my humble opinion:
at the beginning of every function (route), create a timestamp object, in order to registry the exact time when the request was made, independently if it was successful or not
use #app.after_request, for registering every successful request
use #app.errorhandler, for registering general errors + Tracebacks
Here is an example that demonstrates this idea:
#/usr/bin/python3
""" Demonstration of logging feature for a Flask App. """
from logging.handlers import RotatingFileHandler
from flask import Flask, request, jsonify
from time import strftime
__author__ = "#ivanleoncz"
import logging
import traceback
app = Flask(__name__)
#app.route("/")
#app.route("/index")
def get_index():
""" Function for / and /index routes. """
return "Welcome to Flask! "
#app.route("/data")
def get_data():
""" Function for /data route. """
data = {
"Name":"Ivan Leon",
"Occupation":"Software Developer",
"Technologies":"[Python, Flask, JavaScript, Java, SQL]"
}
return jsonify(data)
#app.route("/error")
def get_nothing():
""" Route for intentional error. """
return foobar # intentional non-existent variable
#app.after_request
def after_request(response):
""" Logging after every request. """
# This avoids the duplication of registry in the log,
# since that 500 is already logged via #app.errorhandler.
if response.status_code != 500:
ts = strftime('[%Y-%b-%d %H:%M]')
logger.error('%s %s %s %s %s %s',
ts,
request.remote_addr,
request.method,
request.scheme,
request.full_path,
response.status)
return response
#app.errorhandler(Exception)
def exceptions(e):
""" Logging after every Exception. """
ts = strftime('[%Y-%b-%d %H:%M]')
tb = traceback.format_exc()
logger.error('%s %s %s %s %s 5xx INTERNAL SERVER ERROR\n%s',
ts,
request.remote_addr,
request.method,
request.scheme,
request.full_path,
tb)
return "Internal Server Error", 500
if __name__ == '__main__':
handler = RotatingFileHandler('app.log', maxBytes=10000, backupCount=3)
logger = logging.getLogger(__name__)
logger.setLevel(logging.ERROR)
logger.addHandler(handler)
app.run(host="127.0.0.1",port=8000)
For more information regarding logrotate and logs on stdout and file at the same time: this Gist
If you are using gunicorn to run your Flask app, you can log all Flask exceptions to the gunicorn logs by adding the gunicorn error handlers to the Flask logger:
In module/__init__.py:
#app.before_first_request
def setup_logging():
if not app.debug:
import logging
gunicorn_logger = logging.getLogger('gunicorn.error')
for handler in gunicorn_logger.handlers:
app.logger.addHandler(handler)
In Development, make sure to set: app.config['PROPAGATE_EXCEPTIONS'] = False. Default is None: https://flask.palletsprojects.com/en/1.1.x/config/
I want to show my fancy 404 page in pyramid app, but can get it working. After reading various magic texts about the subject, I put something like this in my code:
cfg.add_view( "Page_not_found_view", renderer="page_404.mak",
context=HTTPNotFound )
But while my *Page_not_found_view* handler is invoked (I can see its' trace) I still get that poor "default" 404 page instead of *my own page_404.mak*. Any ideas?
Here's an example app that uses an exception view to catch the pyramid.httpexceptions.HTTPNotFound view raised by Pyramid when no view can be found that matches:
from waitress import serve
from pyramid.config import Configurator
from pyramid.response import Response
def hello_world(request):
return Response('<html><body>Hello world!</body></html>')
def notfound(request):
return Response('<html><body>Not found!</body></html>')
if __name__ == '__main__':
config = Configurator()
config.add_view(hello_world)
config.add_view(notfound, context='pyramid.httpexceptions.HTTPNotFound')
app = config.make_wsgi_app()
serve(app, host='0.0.0.0')
Visiting '/' will return "Hello world!", visiting "/abc" or "/def" (or anything else that isn't found) will return "Not found!".
What #chris-mcdonough wrote should work in most cases. However, if you are using a matchdict in your view callable and want to show your custom 404 page when nothing matches, make sure that you raise the HTTPNotFound exception instead of returning it. Otherwise, you will get the default 404 page.
Example:
from pyramid import httpexceptions
def my_page(self):
id = self.request.matchdict.get('id', None)
if not id:
raise httpexceptions.HTTPNotFound()
else:
# do whatever here