多模块日志记录时的日志层次结构和根记录器

9

我有这样的设置:

main.py
/module
/module/__init__.py (empty)
/module.py

以下是两个文件的代码,分别为main.pymodule.py

main.py

import logging
from module import module

logger = logging.getLogger(__name__)

def test():
    logger.warning('in main.py/test')

def main():
    handler = logging.StreamHandler()
    handler.setLevel(logging.INFO)
    formatter = logging.Formatter('%(asctime)s %(name)s/%(module)s [%(levelname)s]: %(message)s', '%Y-%m-%d %H:%M:%S')
    handler.setFormatter(formatter)
    logger.addHandler(handler)

    logger.warning('in main.py/main')
    module.something()

if __name__ == "__main__":
    main()    

module.py

import logging
logger = logging.getLogger(__name__)

def something():
    logger.warning('in module.py/something')

我注意到的是,这将输出以下内容(请注意模块日志记录器没有格式):

2019-10-01 09:03:40 __main__/main [WARNING]: in main.py/main
in module.py/something

当我编辑main.pylogger = logging.getLogger( __ name __ )更改为logger = logging.getLogger()或者def main():后添加logger = logging.getLogger()之后,日志记录会像这样(这正是我想要的):

2019-10-01 09:04:13 root/main [WARNING]: in main.py/main
2019-10-01 09:04:13 module.module/module [WARNING]: in module.py/something

为什么会这样呢?我原以为由于main.py导入了module.py,它自然在层级结构上更高,所以module.py会继承在main.py中定义的记录器设置。需要在主模块中显式地设置根记录器(logger = logging.getLogger())才能使继承起作用吗?我是没有正确配置文件夹结构来让module.py的记录器继承main.py的记录器设置,还是文件夹结构无关紧要?
我提出这个问题的原因是因为我认为应该在整个代码中使用logger = logging.getLogger(__name__)(甚至在main.py中),然后根据导入结构(或文件夹结构?),决定层次结构和记录器相应地继承。而我之所以做出这种假设是因为如果我将main.py导入到另一个程序中会怎样呢?我的意思是,我想尽可能地让记录过程变得通用化,这样我就可以将一个模块导入到另一个模块中,并且它总是继承父记录器的设置。有没有一种方法可以显示所有模块的底层层次结构,以进行调试和学习?

拥有一个名为module.py的文件和一个同名的目录(没有.py),其中包含一个__init__.py,这不是一个好主意。 可能会让你自己和任何阅读代码的人感到困惑。 可能有一条规则,说明实际导入的内容,但我认为这不是一个好主意。 - gelonida
代码可以正常工作,我并不觉得它特别令人困惑,而且作为一个示例,它确实突出了我想要传达的内容。我可以将其重命名为module_folder,但这会破坏下面的答案。我认为根据提供的信息,人们应该能够弄清楚... - Mike
我感到困惑的不是代码,而是文件名称。您有一个名为module.py的文件和一个名为module的目录,其中包含一个__init__.py文件。我认为大多数人不会熟记当您执行import module时哪个文件将被查看并编译,是module.py还是module/init.py,或者两者都需要,并且如果两者都需要,又按照什么顺序。如果您使用from module import module导入文件,那么哪个文件将被查看。避免具有相同名称的目录可以使问题更简单。但是您说得对,您已经得到了答案,所以也许只有我感到困惑。 - gelonida
避免使用与文件名.py相同的目录名称可以使事情变得更容易。在我的机器上,module/__init__.py被导入,而module.py被忽略,因此我甚至无法重现您的问题。 - gelonida
1个回答

9
日志层次结构与程序中的文件结构无关。层次结构仅由记录器的名称确定。配置记录器时,所有名称以其名称前缀为子项的记录器都是其子项,并继承其配置,除非另有明确说明。
在您的示例中,日志设置与执行顺序和您选择的名称有很大关系。当程序运行时,它执行以下操作:
1. 由于import logging,从标准库中运行logging.py。 2. 运行module.py以满足from module import module。 3. 将main中的logger属性设置为名为__main__的Logger。 4. 创建一个test函数。 5. 创建一个main函数。 6. 运行主函数。
这个事件序列的一些后果:
- module.logger在main.logger之前创建。这不会影响您正在看到的行为,但在这种情况下值得注意。 - 如果将main作为脚本调用,则main.logger的名称为__main__。如果从python -m main调用它,则您看到的行为不会改变。 - module显然不在与main相同的层次结构中。两者都是根记录器的后代,并沿着不同的分支。
最后一个问题实际上是您的答案。如果要使程序中的所有记录器共享相同的默认记录方法,则应配置根记录器,或确保它们具有相同的名称前缀,然后将其配置为根记录器。
您可以使所有的记录器都继承自main。在module/module.py中,您应该这样做:
logger = logging.getLogger('__main__.' + __name__)

问题在于名称__main__是硬编码的,不能保证它始终是__main__而不是main。您可以尝试在module中使用import main,这样您就可以执行main.__name__ + '.' + __name__,但这并不会按预期工作。如果main__main__形式运行,则导入它实际上将创建第二个模块对象,具有完全独立的日志记录层次结构。
这就是为什么根记录器没有名称的原因。它提供了您想要的确切可维护性和一致性。您不必费心去找出根名称。
话虽如此,您仍应该使main.py记录到__main__main记录器中。根记录器应该只在导入防护中设置。这样,如果main作为常规模块导入,则它将尊重正在运行的驱动程序的日志记录设置。
简而言之,传统方法是在程序的驱动程序中设置匿名根记录器。不要尝试从__main__或驱动程序模块名称继承记录器。

通常情况下,您希望在代码的早期为整个模块分配一个全局可访问的记录器,以便您可以使用它。您不希望每次需要记录器时都去获取它,因此将其分配给全局变量是标准做法。 - Mad Physicist
@Mike。我认为那不完全正确。记录器按名称分层排列,但您不必使用模块的名称。那只是最简单的事情。您可以以任何方式分发记录器,而不考虑模块和其他命名空间。记录器从父记录器继承未明确设置的属性。如果您配置未命名的根记录器,则程序中的所有其他记录器都将继承配置的属性,直到您覆盖它们。从配置文件进行设置几乎与我在此建议的内容互相排斥。 - Mad Physicist
@MadPhysicist,如果我想要在预定义的根记录器名称下跨多个模块进行日志记录,有没有办法?我希望由我的主应用程序调用的模块具有由我的主应用程序定义的前缀。因此,如果我将记录器的名称定义为my_application,则我希望子模块在记录器中具有此名称my_application.sub_modules。这可行吗? - addicted
@addicted。是啊。为什么不呢? - Mad Physicist
问题是我有两个主模块调用一个公共子模块。因此,我不想在子模块的记录器中硬编码应用程序名称。我希望子模块记录器具有动态前缀名称,取决于哪个主应用程序调用它。 - addicted
显示剩余13条评论

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