61

我有一个运行良好的 Flask 应用程序,偶尔会产生错误,当它运行时可见debug=True

if __name__ == '__main__':
    app.run(debug=True)

我收到有用的错误消息,例如:

Traceback (most recent call last):
  File "./main.py", line 871, in index_route

KeyError: 'stateIIIII'

当我在生产中运行应用程序(使用 Lighttpd + fastcgi)时,我希望将这些错误消息保存到文件中。

在查看了各种 StackOverflow 问题后(http://flask.pocoo.org/docs/errorhandling/、http://docs.python.org/2/library/logging.html);Flask 邮件列表;和一些博客,似乎没有简单的方法将所有重要的错误消息发送到文件 - 我需要使用 Python 日志记录模块来自定义内容。所以我想出了以下代码。

在我的应用程序文件的顶部,我有各种导入,然后是:

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)

然后,我将每个路由的代码放在 try/except 语句中,并使用回溯来确定错误来自哪一行并打印出一条不错的错误消息:

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')

然后在文件的末尾,我删除了该debug=True语句。虽然我认为我不需要这样做,因为该应用程序在生产中运行时由 fastcgi 服务器(?)运行。我的应用程序代码的最后两行如下所示:

if __name__ == '__main__':
    app.run()

我正在努力让这个工作。我认为我所管理的最好方法是使用 () 将一条错误日志消息保存在文件中app.logger.error('test message'),但它只打印一条消息。在该错误之后直接记录另一个错误的尝试将被忽略。

4

5 回答 5

69

我不知道为什么它不起作用,但我可以告诉我是如何做到的。

首先,你不需要设置app.logger的级别。所以删除这一行app.logger.setLevel()

您想为每个视图保存异常并返回错误页面。到处编写这段代码需要做很多工作。Flask 提供了一种方法来做到这一点。像这样定义一个错误处理程序方法。

    @app.errorhandler(500)
    def internal_error(exception):
        app.logger.error(exception)
        return render_template('500.html'), 500

每当视图引发异常时,都会调用此方法并将异常作为参数传递。Python logging 提供了异常方法,用于保存异常的完整回溯。

由于这会处理所有异常,因此您甚至不需要将代码放入 try/except 块中。但是,如果您想在调用错误处理程序之前做一些事情(例如回滚会话或事务),请执行以下操作:

    try:
        #code
    except:
        #code
        raise

如果您想为日志文件中的每个条目添加日期和时间,可以使用以下代码(代替问题中的类似代码)。

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)
于 2012-12-26T15:36:32.950 回答
37

对于那些稍后阅读本文的人。

我认为将更多有用的信息推送到错误消息中是更好的主意。URL、客户端 IP、用户代理等。Flask使用函数在内部(在app.debug==False模式下)记录异常。Flask.log_exception因此,我没有手动记录内容,而是@app.errorhandler执行以下操作:

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
        )

然后,在配置时,绑定并FileHandler继续app.logger。我不使用StreamHandler因为许多服务器(例如 uWSGI)喜欢用他们自己专有的冗长无用的不可关闭消息来污染它。

不要害怕扩展 Flask。你迟早会被迫这样做;)

于 2013-12-13T15:26:41.940 回答
16

我不是logging模块方面的专家,但考虑到我在这方面的经验 + 在 Python + Flask 上的几年经验,考虑到一些观察结果,您可以拥有一个良好的日志记录配置:

  • 在每个函数(路由)的开头,创建一个时间戳对象,以便注册请求的确切时间,独立于请求是否成功

  • 使用@app.after_request,用于注册每个成功的请求

  • 使用@app.errorhandler,用于注册一般错误 + Tracebacks

这是一个演示这个想法的例子:

#/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)

有关 logrotate 和同时登录 stdout 和文件的更多信息:this Gist

于 2016-09-02T05:05:48.603 回答
2

如果您使用 gunicorn 运行 Flask 应用程序,则可以通过将 gunicorn 错误处理程序添加到 Flask 记录器来将所有 Flask 异常记录到 gunicorn 日志中:

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)
于 2018-07-08T21:22:45.907 回答
0

在开发中,确保设置:app.config['PROPAGATE_EXCEPTIONS'] = False。默认为无:https ://flask.palletsprojects.com/en/1.1.x/config/

于 2020-11-11T07:57:29.007 回答