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")
Related
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.
I am developing REST API in flask and plan to run it in Gunicorn.
In my appliction, an user-defined Exception was handled by by flask errorhandler decorator. It works fine in both flask build-in web server and Gunicorn. The respone can be generated from decorated function. After introducting flask_restful, the build-in server works fine, but in Gunicorn, the respone is always {"message": "Internal Server Error"}.
Here is the source code: myapp.py
from flask import Flask, jsonify, make_response
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class OrderNotExistError(Exception):
def __init__(self, order_id):
self.message = 'Order [{order_id}] does not exist.'.format(order_id=order_id)
#app.errorhandler(OrderNotExistError)
def order_not_exist(error):
return make_response(jsonify({'message': error.message}), 404)
class OrderAPI(Resource):
def get(self, order_id):
raise OrderNotExistError(order_id)
api.add_resource(OrderAPI, '/orders/<int:order_id>', endpoint='order')
#app.route("/o/<int:order_id>")
def get_order(order_id):
raise OrderNotExistError(order_id)
if __name__ == '__main__':
app.debug = True
app.run()
Run it in Gunicorn:
gunicorn -w4 -b0.0.0.0:8000 myapp:app
Access "http://127.0.0.1:8000/o/123"
It response:
{"message": "Order [123] does not exist."}.
The error handler works fine.
Access "http://127.0.0.1:8000/orders/123"
It Response:
{"message": "Internal Server Error"}.
Seems the error handler does not work.
When run it in flask build-in server, the problem does not occur.
Does anybody meet the same problem?
Is this a bug in flask_restful or Gunicorn?
How to deal with this problem?
This is because there are two levels of error handlers one at the app level and one at the api level. You are making a direct call to the api and therefore the app does not see this. (This explains why the exception is caught for the route added through app.route and not for the one added through api.add_resource).
To catch this error, you need to override Werkzeug's exceptions which is what flask-restful uses. The following code should fix it:
errors={
'InternalServerError': {
'status': 500,
'message': 'Internal Server Error'
},
}
api = Api(app, errors=errors)
Had a similar problem (Flask app using Flask-Restplus):
with Flask build-in werkzeug, #app.errorhandler decorator works perfectly
when run with gunicorn, the default error handler is used (500, Internal Server Error). Solved by deriving Flask-Restplus class and overriding handle_error function to raise the error, letting Flask to handle it.
I have the following Bottle application:
from bottle import Bottle, run, request, response, HTTPResponse
APP1 = Bottle()
#APP1.hook('before_request')
def before_request():
print "APP 1 - Before Request {}".format(request.url)
#APP1.hook('after_request')
def after_request():
print "APP 1 - After Request {}".format(request.url)
print "Response status {}".format(response.status_code)
#APP1.route('/hello')
def hello():
return "Hello World!"
#APP1.route('/error')
def error():
raise HTTPResponse(status=400)
if __name__ == "__main__":
run(APP1, host='127.0.0.1', port=8080)
When I hit the following url: http://127.0.0.1:8080/error I expect a response code of 400 to be printed but instead it prints 200.
APP 1 - Before Request http://localhost:8080/error
APP 1 - After Request http://localhost:8080/error
Response status 200
I know I can use the Plugin concept to implement an around advice but this means we cannot use after_request event hook to process error status codes. Besides it seems counter intuitive.
Is my setup incorrect? Or is my understanding of after_request event hook for Bottle incorrect?
Please advice.
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.
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/