Python日志器在根日志器和我的项目日志器之间的层次结构中是什么?

3
首先,我已经阅读了Real Python关于此主题的文章
在学习了记录器具有层次结构之后,我想创建这个名为MyProjectLogger的新记录器,使其位于此层次结构中:
Root logger
    · MyProjectLogger
         · File logger 1
         · File logger 2
         · ... and so on with all the loggers in my project...

我希望所有的后代记录器都使用MyProjectLogger,因为目前我在项目中的所有记录器中都使用相同的处理程序和相同的配置(相当多)。虽然通过一个方法来实现这一点,但感觉不对。通过这种方式,我只需将处理程序添加到MyProjectLogger中一次,所有后代记录器就可以沿着层次结构向上使用MyProjectLogger的处理程序。
我不想为此使用默认的根记录器,因为我有一些第三方库正在记录它,而现在我希望项目中的记录器与库中的记录器分开记录。
所以,总之:
- 我想在层次结构中定义一个名为MyProjectLogger的记录器 - 我希望它是根记录器的直接后代 - 我希望它是源代码中所有记录器的父级 - 我认为我应该使用propagate=False,这样我就可以将处理程序添加到MyProjectLogger并使其处理后代记录器
我的唯一疑问是:我如何给它一个这样的名称,使其位于根记录器下方且高于其他记录器?
我知道:
logging.getLogger()  # Gets the root logger
logging.getLogger(__name__)  # Gets a logger for the present file
logging.getLogger(__package__)  # Gets a logger for the present module

假设我的项目有以下文件夹布局:

aaaBot/
   main.py  # Only file visible in this example.
            # Please assume the rest of folders have files
   common/
      utils/ 
   config/
   database/
   exceptions/
   model/
   wizards/

在每个文件夹的每个文件中,我使用logging.getLogger(__name__)。在根目录下,__package__返回None,在主执行文件main.py中,__name__'__main__'
我应该为项目中的所有记录器添加前缀+ '.'并创建具有该前缀的MyProjectLogger(例如getLogger(prefix+'.'))吗?
如果不是这样,我该怎么做?

如果我执行 logging.getLogger('aaaBot'),那么每个使用 logging.getLogger(name) 创建的记录器都将位于 aaaBot 记录器下面? - madtyn
没错。例如,如果您在 aaaBot/__init__.py 中使用 root = logging.getLogger(__name__) 配置记录器,则在 aaaBot/common/utils.py 中,logging.getLogger(__name__) 的父级将是 root。记录器名称层次结构源自包/模块名称层次结构,因此您不必显式声明父子关系。 - hoefling
文档中也提到了,查看 Logger Objects 部分。 - hoefling
我现在不在,但今晚(大约10小时后)我会测试这个。你可以发布这个答案,我一旦检查过就会接受它。 - madtyn
我担心___init___.py没有被调用,而我不知道它应该如何被调用。也许我对这里的__init__.py的了解还不够。我现在正在阅读有关此主题的Python官方文档。 - madtyn
显示剩余3条评论
1个回答

3
这是一个工作示例,展示了记录器层次结构模仿模块结构的方式:
so-57021706
└── aaaBot
    ├── __init__.py
    ├── common
    │   ├── __init__.py  # empty
    │   └── utils.py
    └── main.py

Source

aaaBot/__init__.py:

import logging
import sys


PKG_LOGGER = logging.getLogger(__name__)


def setup_logging():
    msg_format = '%(asctime)s [%(levelname)8s] %(message)s (%(name)s - %(filename)s:%(lineno)s)'
    date_format = '%Y-%m-%d %H:%M:%S'
    formatter = logging.Formatter(fmt=msg_format, datefmt=date_format)
    console_handler = logging.StreamHandler(stream=sys.stdout)
    console_handler.setLevel(logging.DEBUG)
    console_handler.setFormatter(formatter)
    PKG_LOGGER.addHandler(console_handler)
    PKG_LOGGER.setLevel(logging.DEBUG)
    PKG_LOGGER.propagate = False
    PKG_LOGGER.info('finished logging setup!')

aaaBot/common/utils.py:

import logging


UTILS_LOGGER = logging.getLogger(__name__)


def spam():
    UTILS_LOGGER.debug('entered spam() function')
    output = 'eggs'
    UTILS_LOGGER.debug('leaving spam() function')
    return output

aaaBot/main.py:

import sys
from aaaBot import setup_logging
from aaaBot.common.utils import spam


if __name__ == '__main__':
    if sys.argv[-1] == '--with-logging':
        setup_logging()
    print(spam())

执行

正常运行:

$ python -m aaaBot.main
eggs

调试运行(开启日志记录):

$ python -m aaaBot.main --with-logging
2019-07-15 13:16:04 [    INFO] finished logging setup! (aaaBot - __init__.py:18)
2019-07-15 13:16:04 [   DEBUG] entered spam() function (aaaBot.common.utils - utils.py:8)
2019-07-15 13:16:04 [   DEBUG] leaving spam() function (aaaBot.common.utils - utils.py:10)
eggs

说明

在这个示例项目中,aaaBot/__init__.py 下的 PKG_LOGGER 是“项目”日志记录器,名称为 aaaBot。 它也是唯一配置的日志记录器; 所有子记录器除了将记录传播到 PKG_LOGGER 之外什么也不做。 子记录器的示例是来自 aaaBot/common/utils.pyUTILS_LOOGER - 没有配置,名称为 aaaBot.common.utils。 在这种情况下,层次结构为:

root logger           code: logging.getLogger()
                      name: "root"
                      configured: no
                      propagates to parent: no (already root)
└── PKG_LOGGER        code: logging.getLogger('aaaBot')
                      name: "aaaBot"
                      configured: yes
                      propagates to parent: no (because of propagate = False)
    └── UTILS_LOGGER  code: logging.getLogger('aaaBot.common.utils')
                      name: "aaaBot.common.utils"
                      configured: no
                      propagates to parent: yes (default behaviour)

配置的可能性是无限的,取决于您特定的用例。例如,您只能配置根记录器,并确保所有记录器传播(它们默认情况下会这样做)。这也将打印您使用的第三方库中的所有消息,如果它们想要记录任何内容的话。或者,您可以引入一个额外的记录器aaaBot.common,它将把来自子记录器的记录写入文件,除了传播和发出到控制台等。
关于您的评论:
似乎这件事对开发库来说还不错,但我的项目是一个应用程序。
这没关系,它只取决于__name__变量的值(导入模块与执行模块之间的区别)。例如,在aaaBot/main.py中使用logging.getLogger(__name__)将不会创建具有名称aaaBot.main且父级为aaaBot的记录器。相反,将创建一个名为__main__的记录器。
库和应用程序之间日志设置的不同之处仅在于记录器配置。如果需要记录日志,则应用程序始终明确配置日志记录。库根本不会配置任何日志记录,除了将NullHandler添加到库的根记录器中。例如,如果aaaBot是一个库,则aaaBot/__init__.py中的日志设置可能如下所示:
import logging
LIB_LOGGER = logging.getLogger('aaaBot')
if not LIB_LOGGER.handlers:
    LIB_LOGGER.addHandler(logging.NullHandler())

那个so-57021706文件夹有关吗?PyCharm在自动补全时做了一些奇怪的事情。 - madtyn
1
不,完全不需要;这是一个在我需要为答案编写一些代码时自动创建的文件夹。你可以放心地忽略它。 - hoefling
1
然而,在这个例子中,你可以把 so-57021706 文件夹看作是 Pycharm 的“源根目录”——也就是 aaaBot 包目录的父级目录。 - hoefling
ValueError: 尝试相对于顶级包之外进行导入。 看起来我需要将所有东西嵌套一层,是吗? - madtyn
看起来你正在使用相对导入,而 sys.path 不包含所需的目录。请查看这个答案以及其中链接的其他答案。 - hoefling

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