在多个模块和多个处理器中使用指定编码进行日志记录的最通用方式是什么?

5 投票
2 回答
7501 浏览
提问于 2025-04-17 16:38

我想要一些具体的建议,关于如何进行多模块和多处理器的日志记录。我在这里添加了我简化的代码,但我不想影响大家的回答——请告诉我最佳实践是什么。

我希望把所有的日志记录到一个文件中,并且在控制台上显示警告及以上级别的信息。

这是我的 level0.py,我希望它能记录到指定的文件:

import logging
from flask import Flask
from level1 import function1

app = Flask(__name__)

logger = logging.getLogger('logger0')
logger.setLevel(logging.DEBUG)

file_handler = logging.FileHandler('../logs/logger0','w','utf-8')
file_handler.setLevel(logging.DEBUG)
file_format = logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d in %(funcName)s]')
file_handler.setFormatter(file_format)
logger.addHandler(file_handler)

console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_format = logging.Formatter('%(message)s')
console_handler.setFormatter(console_format)
logger.addHandler(console_handler)

@app.route('/', methods=['GET', 'POST'])
def function0(foo):
    bar = function1(foo)
    logger.debug('function0')
    ...

另外,level1 可以作为一个独立的模块,当它被当作脚本调用时。在这种情况下,我希望它记录到另一个文件。下面是 level1.py(有重复的日志记录行):

import logging
logger = logging.getLogger('level0.level1')

from level2 import function2

def function1(foo):
    bar = function2(foo)
    logger.debug('function1')
    ...

if __name__ == "__main__":
    logger = logging.getLogger('logger0')
    logger.setLevel(logging.DEBUG)

    file_handler = logging.FileHandler('../logs/logger1','w','utf-8')
    file_handler.setLevel(logging.DEBUG)
    file_format = logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d in %(funcName)s]')
    file_handler.setFormatter(file_format)
    logger.addHandler(file_handler)

    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    console_format = logging.Formatter('%(message)s')
    console_handler.setFormatter(console_format)
    logger.addHandler(console_handler)

    bar = function1('foo')
    logger.info('level1 main')
    ...

我从 level0 复制了这段日志记录的代码,因为我想要相同的日志记录方式,并且把它放在主程序里似乎很直观。level2 不是独立的,所以它只有:

import logging
logger = logging.getLogger('level0.level1.level2')

def function2(foo):
    logger.info('function2')
    ....

我一开始使用 logging.basicSetup,但无法为文件设置编码,每次尝试记录非ASCII字符串时都会出现 UnicodeEncodeError 的错误:

logger.warn(u'foo bar {}'.format(NON_ASCII_STR))

(我仍然会遇到这个错误,因为记录器会把消息传递给它的父级)

那么,在这种情况下,或者更一般地说,对于多个模块,最佳的日志设计是什么?(包括编码选择——我想要使用utf-8)

2 个回答

6

为了总结一下,我做了以下几件事;我在每个模块/文件里都加了这两行:

import logging
logger = logging.getLogger(__name__)

这段代码设置了日志记录,但还没有添加任何处理器。接着,我在我运行的主文件里给根日志记录器添加了处理器,这样导入的模块就能把它们的记录传给根日志记录器,所有的记录都会被保存和显示。我是按照CaptainMurphy的建议来做的,不过我用的是 logger = logging.getLogger('') 来操作根日志记录器。

关于编码的问题 - 我在保存非ASCII字符串到文件时遇到了麻烦。所以我给根日志记录器添加了 FileHandler,这样就可以指定编码了。我用 logging.basicConfig 是做不到这一点的。

再次感谢 @CaptainMurphy - 我很抱歉因为我的声望低无法给你点赞,但我已经勾选了这个答案。

11

对于由多个部分组成的模块,我会使用文档中推荐的方法,简单来说就是每个模块只需要一行代码,logger = logging.getLogger(__name__)。正如你所提到的,模块不应该关心它的信息是怎么处理的,它只需要把信息传递给主程序设置好的日志记录器。

为了减少重复代码,确保你的模块有一个合理的层次结构,并且在某个地方只定义一个设置日志的函数,这样任何主程序都可以调用这个函数。

比如,可以创建一个 logsetup.py 文件:

import logging

def configure_log(level=None,name=None):
    logger = logging.getLogger(name)
    logger.setLevel(level)

    file_handler = logging.FileHandler('../logs/%s' % name,'w','utf-8')
    file_handler.setLevel(logging.DEBUG)
    file_format = logging.Formatter('%(asctime)s %(levelname)s: %(message)s [in %(pathname)s:%(lineno)d in %(funcName)s]')
    file_handler.setFormatter(file_format)
    logger.addHandler(file_handler)

    console_handler = logging.StreamHandler()
    console_handler.setLevel(logging.INFO)
    console_format = logging.Formatter('%(message)s')
    console_handler.setFormatter(console_format)
    logger.addHandler(console_handler)

如果你希望你的各个模块在某种情况下表现得像主程序,可以定义一个单独的函数,比如 main

在 level0.py 和/或 level1.py 中:

def main():
  # do whatever

然后在最顶层的程序中调用这个函数:

import logging
from logsetup import configure_log
configure_log(logging.DEBUG,'level0') # or 'level1'
from level0 import main # or level1

if __name__ == "__main__"
  main()

你仍然需要保留 __name__ == "__main__" 这个条件,因为有些模块(咳咳 multiprocessing 咳咳)的行为会根据这个条件是否存在而有所不同。

撰写回答