如何将Flask出色的debugging日志消息写入生产中的文件?

我有一个Flask应用程序运行良好,并产生偶尔的错误。 当我的应用程序在debugging模式下使用:

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日志logging模块来定制的东西。 所以我想出了下面的代码。

在我的应用程序文件的顶部,我有各种import,其次是:

 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语句中,并使用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') 

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

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

我正在努力得到这个工作。 我认为我所pipe理的最好的方法是使用(app.logger.error('test message'))来获取单个错误日志消息,但它只打印该消息。 在这之后直接logging另一个错误的尝试就被忽略了。

我不知道为什么它不起作用,但我可以告诉我这是怎么回事。

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

你想保存exception并返回每个视图的错误页面。 编写这些代码到处都是很多工作。 Flask提供了一个方法来做到这一点。 定义像这样的error handling方法。

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

每当一个视图产生一个exception时,这个方法将被调用并作为parameter passing这个exception。 Python日志logging提供了用于保存exception完整追溯的exception方法。

由于这处理所有的exception,你甚至不需要把代码放在try / except块中。 但是,如果你想在调用error handling程序(例如回滚会话或事务)之前做一些事情,请执行以下操作:

  try: #code except: #code raise 

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

 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) 

对于那些稍后阅读。

我认为把更有用的信息放入错误信息是更好的主意。 URL,客户端IP,用户代理等Flask.log_exception使用Flask.log_exception函数在内部(以app.debug==False模式)loggingexception。 所以,而不是手动在@app.errorhandler中logging东西,我做这样的事情:

 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 ) 

然后,在configuration时,将FileHandler绑定到app.logger并继续。 我不使用StreamHandler会导致许多服务器(例如uWSGI)喜欢用他们自己专有的 – 罗嗦 – 无用 – 不可转动的消息来污染它。

不要害怕延长酒瓶。 你迟早会被迫做的;)

关于我的经验,我想提出以下意见:

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

  • 使用@ app.errorhandler来注册一般错误(Tracebacks)

  • 在这些装饰器的每一个函数的开始处,总是创build一个时间戳对象,以便注册请求的确切时间,如果它成功或不成功


这里有一个例子来说明这个想法:

 #/usr/bin/env python3 import logging from logging.handlers import RotatingFileHandler from flask import Flask, request, jsonify from time import strftime import traceback app = Flask(__name__) @app.route("/") def get_index(): return "Welcome to Flask! " @app.route("/data") def get_hello(): data = { "Name":"Ivan Leon", "Occupation":"Software Developer", "Technologies":"[Python, Flask, MySQL, Android]" } return jsonify(data) @app.route("/error") def get_json(): return non_existent_variable # ---> intentional <--- @app.after_request def after_request(response): # this if 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): 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=100000, backupCount=3) logger = logging.getLogger('__name__') logger.setLevel(logging.INFO) logger.addHandler(handler) app.run() 

而你的日志文件将是这样的,同时,保持每个HTTP请求的registry在你的屏幕上:

 [2017-Aug-09 01:51] 127.0.0.1 GET http /? 200 OK [2017-Aug-09 01:51] 127.0.0.1 GET http /data? 200 OK [2017-Aug-09 01:51] 127.0.0.1 GET http /error? 5xx INTERNAL SERVER ERROR Traceback (most recent call last): File "/home/ivanlmj/git/env_flask_templates/lib/python3.4/site-packages/flask/app.py", line 1612, in full_dispatch_request rv = self.dispatch_request() File "/home/ivanlmj/git/env_flask_templates/lib/python3.4/site-packages/flask/app.py", line 1598, in dispatch_request return self.view_functions[rule.endpoint](**req.view_args) File "test.py", line 26, in get_json return non_existent_variable # ---> intentional <--- NameError: name 'non_existent_variable' is not defined