Using Flask's "after_request" handler to get tracebacks of errors - python

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.

Related

Hook for processing (not catching) exception in Flask

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.

how to not have the flask server break when an error occurs? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed last year.
Improve this question
I have made an API app using flask, that takes a number(decimal) as input and returns some string. This app breaks if I send a string and works fine after re-starting it. I don't want to restart every time some error happens during processing. How do i do this?
Here is my code:
from flask import Flask, request, jsonify
# initiating the app
flask_api_app = Flask(__name__)
# the app accepts requests via "GET" and "POST" methods
#flask_api_app.route("/", methods=["GET", "POST"])
def output_risk_and_report():
# getting json data from api call
json_data = request.get_json(force=True)
# now the processing part
if json_data['number'] < 2:
return jsonify(["the number is less than two"])
else:
return jsonify(["the number is not less than two"])
# main function, in which app will be run
if __name__ == "__main__":
# running the app on local host
flask_api_app.run(debug=True, host='127.0.0.1', port='8080')
example of call which doesn't break the app: {"number":4}
example of call which breaks the app: {"number":"adfa"}
What changes do I make in my code to accomplish this?
EDIT 1:
i was naive to give that example in my question. in my original program, i may get an error when inserting data into database or may get an error with some arithmetic calculation or something. So, is there any way to tell flask to keep serving for new api requests and not break when an error occurs with only one api call.
When looking into the documents we can find this behaviour:
handle_exception(e)
Handle an exception that did not have an error handler associated with it, or that was raised from an error handler.
This always causes a 500 InternalServerError.
Always sends the got_request_exception signal.
If propagate_exceptions is True, such as in debug mode, the error will
be re-raised so that the debugger can display it. Otherwise, the
original exception is logged, and an InternalServerError is returned.
If an error handler is registered for InternalServerError or 500, it
will be used. For consistency, the handler will always receive the
InternalServerError. The original unhandled exception is available as
e.original_exception.
A way to catch this errors and do something with it could be this:
#app.errorhandler(Exception)
def handle_exception(e):
# pass through HTTP errors
if isinstance(e, HTTPException):
return e
# now you're handling non-HTTP exceptions only
return render_template("500_generic.html", e=e), 500
Source and extra documentation if needed you can find here:
Flask pallets projects
You need to trigger a return with an error message, if whatever the user submits can't be expressed as an integer.
The following should work for submissions like 3 and '3'
# now the processing part
try:
number = int(json_data['number'])
except ValueError:
return jsonify(['Invalid submission'])
# Number is now type integer
if number < 2:
return jsonify(["the number is less than two"])
else:
return jsonify(["the number is not less than two"])
You can make a decorator that is a global exception handler:
import traceback
from flask import current_app
def set_global_exception_handler(app):
#app.errorhandler(Exception)
def unhandled_exception(e):
response = dict()
error_message = traceback.format_exc()
app.logger.error("Caught Exception: {}".format(error_message)) #or whatever logger you use
response["errorMessage"] = error_message
return response, 500
And wherever you create your app instance, you need to do this:
from xxx.xxx.decorators import set_global_exception_handler
app = Flask(__name__)
set_global_exception_handler(app)
This will handle all exceptions generated in your application along with whatever else you need to do to handle them. Hope this helps.
your problem is caused because your program runs in debug mode.
for linux there is a tool called gunicorn. you should set up gunicorn and it will run again your flask app every time it crashes so you wont need to restart by yourself.
i dont know if there is a tool for windows.

How to invalidate or delete a redis cache object if an error in Flask view

I am facing an issue where I am using the Flask cache module and in the case I have a runtime error I don't want the view cached.
Can anyone advise how to do this?
Here is what I would like to achieve.
from flask import Flask
from flask.ext.cache import Cache
app = Flask(__name__)
mycache = Cache()
mycache.init_app(app)
#mycache.cached()
#app.route('/myview')
def process_view():
try:
return get_something()
except:
return print_error_and_no_cache_this_view()
Any thoughts on how I stop this view being cached if I have an error?
You can call cache.delete_memoized in an after_this_request handler:
# #cached needs to be invoked before #route
# because #route registers the handler with Flask
# and #cached returns a different function.
# If you #route first and then #cache Flask will
# always execute the *uncached* version.
#app.route('/myview')
#mycache.cached()
def process_view():
try:
return get_something()
except:
#app.after_this_request
def clear_cache(response):
mycache.delete_memoized('process_view')
return response
return print_error_and_no_cache_this_view()
Alternately, looking at the code for Flask-Cache it appears it does not handle errors thrown by the view. So you could simply raise the error yourself and handle the exception with a separate handler:
#app.route('/myview')
#mycache.cached()
def process_view():
try:
return get_something()
except Exception as e:
raise ProcessViewIssue(e)
#app.errorhandler(ProcessViewIssue)
def handle_process_view_error(e):
return print_error()

Flask and sys.excepthook

I want to add global exception handling object to my Flask webproject. In main module, where application class is created I've added code to override sys.excepthook. Here is simple test code:
import sys
def my_exception_hook(exception_type, value, traceback):
print "My exception handler"
print " Exception type:", exception_type
print " Exception instance:", value
print " Traceback:", traceback
sys.__excepthook__(exception_type, value, traceback)
sys.excepthook = my_exception_hook
from flask import Flask
import requests
app = Flask(__name__)
#app.route('/')
def index():
#Exception raised here "requests.exceptions.MissingSchema" not captured by my handler
r = requests.get('ololo', auth=('user', 'pass'))
return "Hello, world!"
#raise Exception("AAA") #Exception raised here is successfully goes to my_exception_hook
app.run(debug=True, port=5001)
I do not want and have no possibility to envelop each call or requests module with try/catch. Also I want to handle other eaception, for example, mongoDB connection problem which may occured spontaneousely (not wheen I creating connection), please do not suggest it.
Flask already handles exceptions in views for you. Using sys.excepthook is not the right way to handle this, the hook will never be called.
Specify a custom exception handler with Flask instead, see the Redirects and Errors section of the Quickstart manual:
from flask import render_template
#app.errorhandler(500)
def page_not_found(error):
return 'some error message'
Flask also uses the logging module to record exceptions; configure the logging module to write anything of severity logging.ERROR to the console or a log file.
Since you use app.run(debug=True, port=5001), you'll already see exceptions printed to the console.
Note that the 500 error page is only ever invoked when debug is not set to True however; otherwise the Werkzeug debug view is invoked instead.

How do I write Flask's excellent debug log message to a file in production?

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/

Categories