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.
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
: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.
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:
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).
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) withFlask.log_exception
function. So, instead of logging things manually in@app.errorhandler
I do something like this:Then, at configuration time, bind
FileHandler
toapp.logger
and go on. I don't useStreamHandler
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 ;)
I'm not a specialist on
logging
module, but regarding my experience on it + some years of on Python + Flask, you can have a good logging configuration, considering some observations: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:
For more information regarding logrotate and logs on stdout and file at the same time: this Gist