用Pythonic方式将特定信息记录到文件?

0 投票
3 回答
817 浏览
提问于 2025-04-15 12:43

我明白Python的日志记录器不能直接创建,因为文档中提到:

注意,日志记录器从来不是直接实例化的,而是通过模块级别的函数 logging.getLogger(name) 来获取。

这很合理,因为你不应该为每个类或模块都创建日志记录器对象,因为有更好的方法

不过,有些情况下我想要创建一个日志记录器对象,并专门为某个应用的输出附加一个文件,然后关闭这个日志文件。

举个例子,我有一个程序会构建所有在PyPI上的包。基本上可以想象有一个for循环遍历每个包。在循环内部,我想要“创建”一个日志记录器,附加一个文件处理器(比如:/var/logs/pypi/django/20090302_1324.build.log),并将python setup.py build的输出(还有其他内容)发送到这个日志文件。一旦完成,我想要关闭/销毁这个日志记录器,然后继续以类似的方式构建其他包。

所以你看,正常的Python方式调用logging.getLogger在这里不适用。我们需要创建临时的日志记录器对象。

目前,我通过将文件名本身作为日志记录器的名称来实现这一点:

>>> packagelog = logging.getLogger('/var/..../..34.log')
>>> # attach handler, etc..

我想问一下……有没有更好的方法来做到这一点?

3 个回答

0

假设你是通过子进程来调用 setup.py build,那么你其实只需要输出重定向,这个可以通过子进程的调用来实现。

from subprocess import Popen
with open('/var/logs/pypi/django/%s.build.log' % time_str, 'w') as fh:
    Popen('python setup.py build'.split(), stdout=fh, stderr=fh).communicate()

如果你是把 setup.py build 当作一个Python子程序来调用(也就是说,你导入了这个模块并执行它的主程序),那么你可以尝试给这个模块的日志记录器添加另一个 logging.Handler(比如 FileHandler),前提是这个模块里有这样的日志记录器。

更新

根据回答的评论,听起来你只是想要 给当前模块的日志记录器添加一个新的 FileHandler,然后把日志记录到这个处理器里,最后再 从日志记录器中移除它。这样说更符合你的需求吗?

1

这里有两个问题:

  1. 在不同的处理阶段,能够将输出导向不同的日志文件。
  2. 能够将任意命令的标准输出和错误输出重定向到这些日志文件中。

关于第一个问题,我同意ars的回答:他提到的使用多个处理器和一个记录器的方法非常正确。他的格式有点乱,所以我在下面再说一遍:

logger = logging.getLogger("pypibuild")
now_as_string = datetime.datetime.utcnow().strftime("%Y%m%d_%H%M")
for package in get_pypi_packages():
    fn = '/var/logs/pypi/%s/%s.log' % (package, now_as_string)
    h = logging.FileHandler(fn, 'w')
    logger.addHandler(h)
    perform_build(package)
    logger.removeHandler(h)
    h.close()

至于第二个问题,在perform_build()这一步,我假设为了简单起见,我们不需要考虑多核环境。那么,subprocess模块就是你的好帮手。在下面的代码片段中,我省略了错误处理、复杂的格式和其他一些细节,但这应该能给你一个大致的概念。

def perform_build(package):
    logger.debug("Starting build for package %r", package)
    command_line = compute_command_line_for_package(package)
    process = subprocess.Popen(command_line, shell=True,
                               stdin=PIPE, stdout=PIPE, stderr=PIPE)
    stdout, stderr = process.communicate()
    logger.debug("Build stdout contents: %r", stdout)
    logger.debug("Build stderr contents: %r", stderr)
    logger.debug("Finished build for package %r", package)

就这些了。

5

与其使用很多个日志记录器,不如用一个日志记录器配合多个处理器。举个例子:

log = logging.getLogger(name)
while some_condition:
    try:
        handler = make_handler(filename)
        log.addHandler(handler)
        # do something and log

    finally:
        log.removeHandler(handler)
        handler.close()

撰写回答