【问题标题】:Python logger in the hierarchy under root and over my project loggers?根目录下和我的项目记录器上的层次结构中的 Python 记录器?
【发布时间】:2019-07-13 18:14:00
【问题描述】:

首先,我读过real python article on the subject

了解到记录器有一个层次结构,我想在这样的层次结构中创建一个名为 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+'.'))?

如果没有,我该怎么办?

【问题讨论】:

  • 应该MyProjectLogger 是所需的名称吗?因为logging.getLogger('aaaBot') 已经是您项目层次结构中的根记录器。
  • 那么,如果我做 logging.getLogger('aaaBot'),那么使用 logging.getLogger(____name____) 创建的每一个都将在 aaaBot logger 下?
  • 没错。如果你例如在aaaBot/__init__.py 中配置一个记录器,使用root = logging.getLogger(__name__),然后是logging.getLogger(__name__) 的父级,例如aaaBot/common/utils.py 将是 root。出于这个原因,记录器名称层次结构派生自包/模块名称层次结构,因此您不必显式声明父子关系。
  • 文档中也提到过,请查看Logger Objects下的部分。
  • 我在外面,但我在晚上在这里测试这个(大约 10 小时)。欢迎您发布此答案,一旦我检查它,我就会接受它。

标签: python python-3.x logging


【解决方案1】:

这是一个工作示例,说明模仿模块结构的记录器层次结构:

so-57021706
└── aaaBot
    ├── __init__.py
    ├── common
    │   ├── __init__.py  # empty
    │   └── utils.py
    └── main.py

来源

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 在自动完成时做了一些奇怪的事情
  • 不,一点也不;它是一个文件夹,当我需要为答案编写一些代码时会自动创建它。您可以放心地省略它。
  • 但是,您可以将示例中的 so-57021706 文件夹视为 Pycharm 的“源根目录”——aaaBot 包目录的父目录。
  • ValueError: attempted relative import beyond top-level package 好像我需要将所有内容都嵌套一层,不是吗?
  • 看起来您正在使用相对导入,而 sys.path 不包含所需的目录。查看this answer 和其中的链接。
猜你喜欢
  • 2023-03-31
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多