Home > Software engineering >  How to log uncaught exceptions in Flask routes with logging?
How to log uncaught exceptions in Flask routes with logging?

Time:10-25

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

  • Related