【问题标题】:Python: flush logging only at end of script runPython:仅在脚本运行结束时刷新日志记录
【发布时间】:2017-10-16 03:42:33
【问题描述】:

目前我用于记录一个自定义日志系统,其工作方式如下:
我有一个类似于以下内容的日志class

class Log:
    def __init__(self):
        self.script = ""
        self.datetime = datetime.datetime.now().replace(second=0, microsecond=0)
        self.mssg = ""
        self.mssg_detail = ""
        self.err = ""
        self.err_detail = ""

我创建了一个函数装饰器,它在函数调用上执行try/except,并相应地在Log 对象上向.mssg.err 添加一条消息。

def logging(fun):
    @functools.wraps(fun)
    def inner(self, *args):
        try:
            f = fun(self, *args)
            self.logger.mssg += fun.__name__ +" :ok, "                
            return f               
        except Exception as e:
            self.logger.err += fun.__name__ +": error: "+str(e.args) 
    return inner 

所以通常一个脚本是一个由多个按顺序运行的方法组成的类。 因此我运行这些方法(如上面提到的装饰),最后我将 Log 对象上传到 mysql 数据库中。

这很好用。但现在我想修改这些项目,使它们与 python 的“官方”日志模块集成。

我不喜欢该模块的地方在于,无法将消息“保存”到 1 个日志对象上,以便仅在运行结束时上传/保存到日志。相反,每个日志调用都会将消息写入/发送到文件等 - 这有时会产生很多性能问题。我可以使用handlers.MemoryHandler,但它似乎仍然不像我原来的系统那样运行:据说它会定期收集消息并将它们刷新到另一个处理程序 - 这不是我想要的:我想收集内存中的消息并根据请求使用显式函数刷新它们。

有人有什么建议吗?

【问题讨论】:

  • 顺便说一下,你应该在你的装饰器中重新引发异常。
  • @Laurent LAPORTE 不,重点是脚本一直运行到最后,以便能够上传日志......
  • 这是您的选择,但不是一个好的做法。例如,您如何处理KeyboardInterrupt
  • "但是现在我想修改这些项目,以便它们与 python 的“官方”日志记录模块集成。"你的意思是?为什么要这样做?
  • 我的意思是装饰器方法中的表达式self.logger.err += fun.__name__ +": error: "+str(e.args)等将被logging.error(fun.__name__ +": error: "+str(e.args))替换。类似地,Log类声明将被basicConfig日志声明替换。我这样做是因为我需要将我的代码与另一个使用主要 python 日志记录模块的库集成。

标签: python


【解决方案1】:

这是我的想法。使用处理程序捕获 StringIO 中的日志。然后,您可以随时获取 StringIO。由于讨论线程中可能存在一些混淆 - StringIO 是字符串的“类文件”接口,因此从未涉及实际文件。

import logging
import io
def initialize_logging(log_level, log_name='default_logname'):
    logger = logging.getLogger(log_name)
    logger.setLevel(log_level)

    log_stream = io.StringIO()

    if not logger.handlers:
        ch = logging.StreamHandler(log_stream)
        ch.setLevel(log_level)
        ch.setFormatter(logging.Formatter(
            '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
        ))
        logger.addHandler(ch)
        logger.propagate = 0
    return logger, log_stream

然后是这样的:

>>> logger, log_stream = initialize_logging(logging.INFO, "logname")
>>> logger.warning("Hello World!")

当你想要日志信息时:

>>> log_stream.getvalue()
'2017-05-16 16:35:03,501 - logname - WARNING - Hello World!\n'

【讨论】:

【解决方案2】:

在程序启动时(在主程序中),您可以:

  • 实例化您的自定义记录器 => 全局变量/单例。
  • 在程序结束时注册一个函数,该函数将刷新您的记录器。
  • 运行您的装饰函数。

要注册一个函数,你可以使用atexit.register函数。请参阅文档中的页面Exit handlers

编辑

上面的思路可以简化。

要延迟记录,您可以使用标准的MemoryHandler 处理程序,在页面logging.handlers — Logging handlers 中进行了描述

看看这个 GitHub 项目:https://github.com/tantale/python-ini-cfg-demo

并用这个替换INI文件:

[formatters]
keys=default

[formatter_default]
format=%(asctime)s:%(levelname)s:%(message)s
class=logging.Formatter

[handlers]
keys=console, alternate

[handler_console]
class=logging.handlers.MemoryHandler
formatter=default
args=(1024, INFO)
target=alternate

[handler_alternate]
class=logging.StreamHandler
formatter=default
args=()

[loggers]
keys=root

[logger_root]
level=DEBUG
formatter=default
handlers=console

要记录到数据库表,只需将 alternate 处理程序替换为您自己的数据库处理程序即可。

有一些关于此的博客/SO 问题:

EDIT2

注意:ORM 一般支持“急切加载”,例如SqlAlchemy

【讨论】:

  • 这是一个有趣的指针谢谢!它没有回答我帖子的主要关注点(即我想摆脱我的自定义记录器并用 python 日志记录实用程序替换它)但它绝对是我可以用来改进我的代码的东西。
  • 如果你使用数据库,你肯定有一个可以配置的缓存。您可以将其与标准记录器一起使用。
  • @LaurentLAPORTE 我刚刚给出了一个回答特定问题的答案。但当然,更明显的答案是您的站点,编写一个直接进入数据库的处理程序。我想底线是......编写一个处理程序来做你想要的。
  • @LaurentLAPORTE 好的,谢谢。我已经在我的初始帖子中提到了memoryhandler。问题是当缓冲区已满或发生具有指定严重性级别的事件时,它们会被刷新(根据文档)。这些选项都没有让我满意,我不知道如何绕过它们。我希望能够将所有日志上传到数据库,明确调用一个函数。顺便说一句,你认为我可以使用docs.python.org/3/library/logging.html#logrecord-objects 吗?只是丢弃所有实际的日志事件,然后在退出上传所有日志记录?
  • 你可以使用非常大的MemoryHandler...是的,你可以使用LogRecord object:它是一个简单的扁平结构。
猜你喜欢
  • 2012-01-06
  • 2013-05-14
  • 2020-06-26
  • 2022-07-27
  • 2021-09-25
  • 2010-10-31
  • 1970-01-01
  • 2017-12-18
  • 1970-01-01
相关资源
最近更新 更多