使用指定编码记录多个模块和多个处理程序的最Python方式是什么?


问题内容

我正在寻找有关应如何完成多个模块和多个处理程序日志记录的具体建议。我在这里添加了简化的代码,但是我不想偏见答案-告诉我最佳实践是什么。

我想将所有内容都记录到一个文件中,并进行警告和以上操作以进行控制台。

这是我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,因为我想要相同的日志记录,并且将其放入main似乎很直观。level2不是独立的,因此只有:

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

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

我从开始logging.basicSetup,但无法设置文件的编码,并UnicodeEncodeError在尝试记录非ascii字符串时一直获取:

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

(当记录器将消息传递给其父母时,我仍然会收到错误消息)

那么,对于这种情况或更一般而言,最佳的日志设计是什么-多个模块(具有编码选择-我想要utf-8)


问题答案:

对于由许多部分组成的模块,我使用文档中推荐的方法,每个模块只有一行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__"那里找到该子句,某些模块( cough multiprocessing cough
)的行为取决于该子句是否存在而有所不同。