What is the standard way to log uncaught expressions in Flask routes with logging
?
This nearly works:
import logging, sys, flask
logging.basicConfig(filename='test.log', filemode='a', format='%(asctime)s %(levelname)s %(message)s')
sys.excepthook = lambda exctype, value, tb: logging.error("", exc_info=(exctype, value, tb))
logging.warning("hello")
app = flask.Flask('hello')
@app.route('/')
def index():
sjkfq # uncaught expresion
return "hello"
app.run()
but there are some ANSI escape characters in the log: [31m[1m
etc. (probably for console colors)
See here in the produced test.log
file:
2022-10-21 16:23:06,817 WARNING hello
2022-10-21 16:23:07,096 INFO [31m[1mWARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.[0m
* Running on http://127.0.0.1:5000
2022-10-21 16:23:07,097 INFO [33mPress CTRL C to quit[0m
2022-10-21 16:23:07,691 ERROR Exception on / [GET]
Traceback (most recent call last):
File "C:\Python38\lib\site-packages\flask\app.py", line 2525, in wsgi_app
response = self.full_dispatch_request()
File "C:\Python38\lib\site-packages\flask\app.py", line 1822, in full_dispatch_request
rv = self.handle_user_exception(e)
File "C:\Python38\lib\site-packages\flask\app.py", line 1820, in full_dispatch_request
rv = self.dispatch_request()
File "C:\Python38\lib\site-packages\flask\app.py", line 1796, in dispatch_request
return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
File "D:\test.py", line 10, in index
sjkfq
NameError: name 'sjkfq' is not defined
2022-10-21 16:23:07,694 INFO 127.0.0.1 - - [21/Oct/2022 16:23:07] "[35m[1mGET / HTTP/1.1[0m" 500 -
Note that this is totally reproducible if you run the same code.
Is there a documented way to do proper logging of uncaught exceptions in Flask routes, with Python logging
? I didn't exactly find this in https://flask.palletsprojects.com/en/2.2.x/logging/.
(Note: this code shows it's not the right way to do logging. We shouldn't intercept a logging string, do some reverse engineering to clean the escape characters, and then log to a file. There surely is a a standard logging way.)
CodePudding user response:
So the problem is that those ANSII characters [31m[1m
specify the colour of the message in terminal.
To my understanding Flask takes logs from werkzeug and adds some colouring.
As you can see from this issue, there is no way to disable colourings in flask logging by any simple flag in a set up (unfortunately)
What you could do is to add formatter that will get message and remove colour stylings before logging.
import logging, sys, flask
import click
class NoColorFormatter(logging.Formatter):
def format(self, record: logging.LogRecord) -> str:
return click.unstyle(super().format(record))
werkzeug_logger = logging.getLogger("werkzeug")
root_logger = logging.getLogger()
handler = logging.FileHandler("test.log")
handler.setFormatter(NoColorFormatter("%(asctime)s %(levelname)s %(message)s"))
werkzeug_logger.addHandler(handler)
root_logger.addHandler(handler)
sys.excepthook = lambda exctype, value, tb: logging.error(
"", exc_info=(exctype, value, tb)
)
logging.warning("hello")
app = flask.Flask("hello")
@app.route("/")
def index():
sjkfq # uncaught expresion
return "hello"
if __name__ == "__main__":
app.run()
CodePudding user response:
I use a decorator to catch exceptions, this can also log the error:
from flask import abort
from functools import wraps
def catch_uncaught(function):
"""
catches uncaught exceptions and logs them
aborts the request
"""
@wraps(function)
def wrapper(*args, **kwargs):
try:
res = function(*args, **kwargs)
return res # no errors occurred
except Exception as e:
# log stack trace
logging.error("\n" traceback.format_exc() "\n")
# abort the request instead of crashing
abort(500, "Internal Server error, do not retry")
return wrapper
(This will also abort the request instead of crashing the server - but this is "optional". if you prefer to crash, return function(*args, **kwargs)
after logging instead of abort)
you can then put this decorator on top of the routes you want to catch exceptions from to log them:
@app.route("/")
@catch_uncaught
def index():
sjkfq # uncaught exception
return "hello"
This probably isn't the standard way, but it works quite well