如何将Flask优秀的调试日志消息写入生产中的文件?

Ben*_*Ben 57 python logging flask

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

if __name__ == '__main__':
    app.run(debug=True)
Run Code Online (Sandbox Code Playgroud)

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

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

KeyError: 'stateIIIII'
Run Code Online (Sandbox Code Playgroud)

我想在生产中运行应用程序(使用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)
Run Code Online (Sandbox Code Playgroud)

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

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')
Run Code Online (Sandbox Code Playgroud)

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

if __name__ == '__main__':
    app.run()
Run Code Online (Sandbox Code Playgroud)

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

cod*_*ool 60

我不知道为什么它不起作用,但我可以告诉我这是怎么做的.

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

您希望为每个视图保存异常并返回错误页面.在任何地方编写此代码都需要做很多工作.Flask提供了一种方法.定义像这样的错误处理程序方法.

    @app.errorhandler(500)
    def internal_error(exception):
        app.logger.error(exception)
        return render_template('500.html'), 500
Run Code Online (Sandbox Code Playgroud)

每当视图引发异常时,将调用此方法并将异常作为参数传递.Python日志记录提供了异常方法,用于保存异常的完整回溯.

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

    try:
        #code
    except:
        #code
        raise
Run Code Online (Sandbox Code Playgroud)

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

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)
Run Code Online (Sandbox Code Playgroud)

  • 啊哈。弄清楚了。默认情况下,400 错误(request.form 上的关键错误)不会被记录为真正的异常,但 500 是。 (2认同)

Iva*_*nin 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
        )
Run Code Online (Sandbox Code Playgroud)

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

不要害怕延长Flask.你迟早会被迫这样做;)


iva*_*ncz 10

我不是logging模块的专家,但根据我的经验+在Python + Flask上的几年,你可以有一个很好的日志配置,考虑一些观察:

  • 在每个函数(路由)的开头,创建一个时间戳对象,以便在发出请求时注册确切的时间,如果成功则不成功

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

  • 使用@ app.errorhandler来注册常规错误+回溯

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

#/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)
Run Code Online (Sandbox Code Playgroud)

有关logrotate和stdout和文件同时登录的更多信息:这个要点