Flask日志记录 - 无法将其写入文件

73

好的,这是我设置一切的代码:

if __name__ == '__main__':
    app.debug = False

    applogger = app.logger

    file_handler = FileHandler("error.log")
    file_handler.setLevel(logging.DEBUG)

    applogger.setLevel(logging.DEBUG)
    applogger.addHandler(file_handler)

    app.run(host='0.0.0.0')

发生的情况是

  1. 错误日志被创建
  2. 从未有任何内容被写入
  3. 尽管没有添加 StreamHandler 并将调试设置为 false,我仍然在 STDOUT 中看到所有内容(这可能是正确的,但仍然似乎很奇怪)

我是否完全错了或者出了什么问题?

7个回答

128

为什么不这样做:

if __name__ == '__main__':
    init_db()  # or whatever you need to do

    import logging
    logging.basicConfig(filename='error.log',level=logging.DEBUG)

    app.run(host="0.0.0.0")

如果现在启动应用程序,您将看到error.log包含以下内容:

INFO:werkzeug: * Running on http://0.0.0.0:5000/

欲了解更多信息,请访问http://docs.python.org/2/howto/logging.html

好的,既然您坚持使用我所展示的方法无法具备两个处理器,我将添加一个例子来清晰说明。首先,在主函数中添加以下记录代码:

import logging, logging.config, yaml
logging.config.dictConfig(yaml.load(open('logging.conf')))

现在添加一些调试代码,以便我们看到我们的设置是否有效:

logfile    = logging.getLogger('file')
logconsole = logging.getLogger('console')
logfile.debug("Debug FILE")
logconsole.debug("Debug CONSOLE")

现在只剩下"logging.conf"程序了。让我们使用它:

version: 1
formatters:
  hiformat:
    format: 'HI %(asctime)s - %(name)s - %(levelname)s - %(message)s'
  simple:
    format: '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
handlers:
  console:
    class: logging.StreamHandler
    level: DEBUG
    formatter: hiformat
    stream: ext://sys.stdout
  file:
    class: logging.FileHandler
    level: DEBUG
    formatter: simple
    filename: errors.log
loggers:
  console:
    level: DEBUG
    handlers: [console]
    propagate: no
  file:
    level: DEBUG
    handlers: [file]
    propagate: no
root:
  level: DEBUG
  handlers: [console,file]

这个配置比需要的更为复杂,但也展示了 logging 模块的一些特性。

现在,当我们运行应用程序时,我们会看到这个输出(werkzeug- 和 console-logger):

HI 2013-07-22 16:36:13,475 - console - DEBUG - Debug CONSOLE
HI 2013-07-22 16:36:13,477 - werkzeug - INFO -  * Running on http://0.0.0.0:5000/

还要注意使用了带有“HI”选项的自定义格式化程序。

现在看看“errors.log”文件。它包含:

2013-07-22 16:36:13,475 - file - DEBUG - Debug FILE
2013-07-22 16:36:13,477 - werkzeug - INFO -  * Running on http://0.0.0.0:5000/

2
因为我实际上想要实现几个记录不同级别日志的处理程序,而这种方法并不能让我走得太远。 - fleshgolem
@fleshgolem:你仍然可以创建多个处理程序。只需直接使用Python的记录功能即可实现。 - HolgerSchurig
@fleshgolem:看一下我给你的URL,然后搜索logging.conf。在那里,你会看到如何创建第二个记录器。你有'root'和'simpleExample'两个记录器,它们可以有不同的日志级别,例如。 - HolgerSchurig
好的,我不是来打架的,这确实是一个非常好的和详细的答案,所以你可以拿到勾选标记。 - fleshgolem
19
这并没有真正使用 Flask 提供的 app.logger。 - Milimetric
2
app.logger 的问题在于它完全是硬编码的。至少在 Flask 0.12.2 中,Flask 的日志记录设置(日志级别、日志格式和日志输出流)都是硬编码在 flask/logging.py 中的,无法在不覆盖 app.logger 属性并编写自定义实现的情况下更改。 - alexykot

16

好的,我的失败源于两个误解:

1)Flask 显然只在运行生产模式时才会忽略所有自定义日志记录

2)debug=False 不足以使其运行于生产模式。您必须使用任何类型的 WSGI 服务器来包装应用程序。

在我从 gevent 的 WSGI 服务器启动应用程序之后(并将日志初始化移动到更合适的位置),一切似乎都正常工作了。


5
你的结论是错误的。我建议你使用的代码可以 a) 在内置服务器中运行,b) 在测试和/或调试模式下运行,c) 使用多个处理程序。此外,你最初的问题是“无法将其写入文件”,我已经解决了这个问题。不幸的是,你在中途扩大了问题,并想要不同的东西... 下次,我建议你更准确地描述你的问题。 - HolgerSchurig
3
我不完全同意。我问为什么Flask的内部记录器不会写入文件,您提供了一个规避该记录器的解决方案。是的,它能够工作,但我并不认为这是一个确切的答案。不过我同意,我表述问题的方式很糟糕。我将两者都保留开放,这样任何找到这篇文章的人都可以从中获得帮助。 - fleshgolem
4
没有“Flask内部日志记录器”。Flask使用(并配置)Python logging模块中的日志记录器。实际上,我在flask/logging.py中看到语句“from logging import getLogger,…”之前,我也觉得Flask的日志记录文档很令人困惑。从那时起,就很容易理解了,因为Python logging模块有非常好的文档说明。所以,我没有规避Flask的日志记录器。如果我这样做了,Flask的日志输出怎么可能出现在errors.log中呢? - HolgerSchurig

13

您在应用程序控制台中看到的输出来自底层的Werkzeug记录器,可以通过logging.getLogger('werkzeug')进行访问。

通过为Flask和该记录器添加处理程序,您的日志记录可以在开发和发布中都起作用。

更多信息和示例代码请参见:将Flask请求写入访问日志


4
在我自己探索了一段时间后,发现了这个“werkzeug”记录器是所有实际的HTTP日志记录发生的地方。不幸的是,它不会格式化日志,而是将'%s - - [%s] %s\n' %预先格式化推送到记录器中。因此,如果您想要相同风格的日志,您需要在本地重新创建该格式或调用werkzeug._internal._log方法。 - Pyrce
对我来说,logging.Logger.manager.loggerDict 中没有包含 werkzeug 日志,直到我按照 @David 的回答使用 @app.before_first_request 进行修饰,我的“模块顶部”的配置才起作用。 - xenoclast
1
谢谢!这是唯一让我把 Flask 日志记录到系统日志的信息。 - Rick Berge
对于所有想知道的人:这是正确的答案,也是最简单的答案。如果你想访问Flask的记录器,就是这个。 - waykiki

7
这可以工作:
if __name__ == '__main__':
    import logging
    logFormatStr = '[%(asctime)s] p%(process)s {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s'
    logging.basicConfig(format = logFormatStr, filename = "global.log", level=logging.DEBUG)
    formatter = logging.Formatter(logFormatStr,'%m-%d %H:%M:%S')
    fileHandler = logging.FileHandler("summary.log")
    fileHandler.setLevel(logging.DEBUG)
    fileHandler.setFormatter(formatter)
    streamHandler = logging.StreamHandler()
    streamHandler.setLevel(logging.DEBUG)
    streamHandler.setFormatter(formatter)
    app.logger.addHandler(fileHandler)
    app.logger.addHandler(streamHandler)
    app.logger.info("Logging is set up.")
    app.run(host='0.0.0.0', port=8000, threaded=True)

说实话,它看起来很笨重和混乱。使用dictConfig怎么样? - codingbruh

5

我不喜欢其他的答案,所以继续努力,似乎我必须在Flask完成自己的设置之后才能制作我的日志配置。

@app.before_first_request
def initialize():

    logger = logging.getLogger("your_package_name")
    logger.setLevel(logging.DEBUG)
    ch = logging.StreamHandler()
    ch.setLevel(logging.DEBUG)
    formatter = logging.Formatter(
    """%(levelname)s in %(module)s [%(pathname)s:%(lineno)d]:\n%(message)s"""
    )
    ch.setFormatter(formatter)
    logger.addHandler(ch)

我的应用程序的结构是这样的

/package_name
    __main__.py <- where I put my logging configuration
    __init__.py <- conveniance for myself, not necessary
    /tests
    /package_name <- Actual flask app
    __init__.py
    /views
    /static
    /templates
    /lib

按照这些指示http://flask.pocoo.org/docs/0.10/patterns/packages/进行操作。

我的问题不是关于记录文件,而是在Docker中将模块日志与Flask日志结合起来并进行过滤,尽管我无法使其正常工作的原因似乎是相同的(Flask篡夺了记录器)。这个答案给了我我所需要的! - xenoclast

4
为什么不深入代码看一看呢...
我们要查看的模块是`flask.logging.py`,它定义了一个名为`create_logger(app)`的函数。检查该函数将提供一些线索,有助于排除使用Flask进行日志记录时出现的问题。
编辑:此答案适用于版本1之前的Flask。此后,`flask.logging.py`模块已经发生了很大变化。答案仍然有助于在python日志记录方面提供一些普遍的注意事项和建议,但请注意,Flask在这方面的某些特殊性已在版本1中得到解决,可能不再适用。
该函数中冲突的第一个可能原因是以下行:
logger = getLogger(app.logger_name)

让我们来看看原因:
变量 app.logger_name 是在 Flask.__init__() 方法中设置的,其值为 import_name。而 import_name 本身是 Flask(__name__) 的接收参数。也就是说,app.logger_name 被赋值为 __name__ 的值,__name__ 很可能是您的主包的名称,例如在这个例子中叫做'awesomeapp'。
现在,假设您决定手动配置并创建自己的记录器。如果您的项目名称为“awesomeapp”,那么您认为您使用该名称来配置记录器的几率有多大?我认为这很有可能。
my_logger = logging.getLogger('awesomeapp') # doesn't seem like a bad idea
fh = logging.FileHandler('/tmp/my_own_log.log')
my_logger.setLevel(logging.DEBUG)
my_logger.addHandler(fh)

这样做是有道理的...但还存在一些问题。
当首次调用 Flask.logger 属性时,它将依次调用函数 flask.logging.create_logger() ,并会随之发生以下操作:
logger = getLogger(app.logger_name)

记得你之前是如何以项目名称来命名你的日志记录器,而app.logger_name也与这个名称相同吗?在上面这行代码中,logging.getLogger()函数已经检索到了你之前创建的日志记录器,并且接下来的指令将会以一种让你后悔不已的方式对其进行修改。例如:
del logger.handlers[:]

瞬间,您可能先前已经注册的日志处理程序全部消失了。

函数内还有其他事情发生,不再详细说明。它创建和注册两个 logging.StreamHandler 对象,可以输出到 sys.stderr 和/或 Response 对象。一个用于记录级别为“debug”,另一个用于“production”。

class DebugLogger(Logger):
    def getEffectiveLevel(self):
        if self.level == 0 and app.debug:
            return DEBUG
        return Logger.getEffectiveLevel(self)

class DebugHandler(StreamHandler):
    def emit(self, record):
        if app.debug and _should_log_for(app, 'debug'):
            StreamHandler.emit(self, record)

class ProductionHandler(StreamHandler):
    def emit(self, record):
        if not app.debug and _should_log_for(app, 'production'):
            StreamHandler.emit(self, record)

debug_handler = DebugHandler()
debug_handler.setLevel(DEBUG)
debug_handler.setFormatter(Formatter(DEBUG_LOG_FORMAT))

prod_handler = ProductionHandler(_proxy_stream)
prod_handler.setLevel(ERROR)
prod_handler.setFormatter(Formatter(PROD_LOG_FORMAT))

logger.__class__ = DebugLogger
logger.addHandler(debug_handler)
logger.addHandler(prod_handler)

有了上述细节,我们可以更清楚地了解当 Flask 参与时为什么手动配置的日志记录器和处理程序会出现问题。不过,新的信息为我们提供了新的选择。如果您仍想保持单独的处理程序,最简单的方法是将记录器命名为与项目不同的名称(例如,my_logger = getLogger('awesomeapp_logger'))。另一种方法,如果您想与 Flask 中的日志记录协议保持一致,是使用与 Flask 类似的方法在 Flask.logger 上注册一个 logging.FileHandler 对象。
import logging
def set_file_logging_handler(app):

    logging_path = app.config['LOGGING_PATH']

    class DebugFileHandler(logging.FileHandler):
        def emit(self, record):
            # if your app is configured for debugging
            # and the logger has been set to DEBUG level (the lowest)
            # push the message to the file
            if app.debug and app.logger.level==logging.DEBUG:
                super(DebugFileHandler, self).emit(record)

    debug_file_handler = DebugFileHandler('/tmp/my_own_log.log')
    app.logger.addHandler(debug_file_handler)

app = Flask(__name__)
# the config presumably has the debug settings for your app
app.config.from_object(config)
set_file_logging_handler(app)

app.logger.info('show me something')

你好,Michael。你提到了"When the Flask.logger property is invoked for the first time it will in turn call the function flask.logging.create_logger()" - 你能告诉我Flask.logger是在什么时候被调用的吗?是在使用app = Flask(__name__)初始化flask应用程序时还是在调用flask应用程序记录器时(例如:app.logger.info('test'))? - variable
@variable 我已经有一段时间没有看过这段代码了,但我认为应该是后者。如果你不使用它,创建日志记录器的实例就没有意义。如果Flask有一个名为logger@property,那么请查看源代码。我猜代码从那里开始。 - Michael Ekoka
在 app.py 中,有一个名为 @locked_cached_property 的函数,其中包含 def logger(self): 函数,负责检查/添加记录器/处理程序的逻辑。但我无法弄清楚是谁或如何调用该函数 - https://github.com/pallets/flask/blob/2062d984abd3364557a6dcbd3300cfe3e4ecf156/src/flask/app.py#L631 - variable
@variable @property 是实现getter/setter/deleter的Pythonic方式。在这里查看有关它们的信息:https://dev59.com/Ymw15IYBdhLWcg3wbbNx。如果您不理解`@property`语法,请了解*python装饰器*。您可以学习使用`property()`来实现getter/setter,而无需了解其底层工作原理,这涉及到剥离更多层(请参见*python描述符协议*)。虽然`property()`函数是Python本地的,但`locked_cache_property()`似乎是flask的一种专门化。 - Michael Ekoka
这是一个装饰器(https://github.com/pallets/flask/blob/2062d984abd3364557a6dcbd3300cfe3e4ecf156/src/flask/helpers.py#L915),它将函数转换为懒惰的属性。但是它并没有提到什么时候调用create_logger。请问有什么建议吗? - variable
@variable 这个可能需要解释一下,你能加入这个聊天室吗?https://chat.stackoverflow.com/rooms/211241/flask-logging-decorator? - Michael Ekoka

0

日志快速入门

-- 这段代码在类或导入中不适用于多个日志文件

import logging
import os # for Cwd path 
path = os.getcwd()


logFormatStr = '%(asctime)s  %(levelname)s - %(message)s'
logging.basicConfig(filename=path + '\logOne.log', format=logFormatStr, level=logging.DEBUG), logging.info('default message')

多个日志文件

使用logging.getLogger()方法创建一个日志实例---

  1. 每个日志文件都需要一个日志实例
  2. 我们可以创建多个日志文件,但不能使用同一个实例

使用名称或Hardcore_String创建新的日志实例 ----首选(名称),这将指定确切的类从哪里调用

logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)

日志类型 -- INFO、DEBUG、ERROR、CRITICAL、WARNING

DEBUG----详细信息,通常只在诊断问题时才有兴趣。

INFO----确认一切正常工作。

WARNING----表示发生了意外情况,或者预示着将来可能出现某些问题(例如,“磁盘空间不足”)。软件仍然按预期工作。

ERROR----由于更严重的问题,软件无法执行某些功能。

CRITICAL----严重错误,表明程序本身可能无法继续运行。

创建新格式化程序

format = logging.Formatter('%(asctime)s %(levelname)s - %(message)s')

创建新的文件处理程序。
file_handel = logging.FileHandler(path + '\logTwo.log')

将格式设置为FileHandler并将file_handler添加到日志实例[logger]中

file_handel.setFormatter(format)
logger.addHandler(file_handel)

在logOne.log和logTwo.log中添加一条记录,并分别设置级别。
logger.info("message for logOne")
logging.debug(" message for logTwo")

了解更多细节


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接