python 从多个包中记录?

python logging from multiple packages?

我正在导入我构建到 Python 脚本中的 2 个模块。我想从脚本加上两个模块的日志记录到一个日志文件中。 logging cookbook and related forum posts (example) 显示了如何为 one 脚本和 one 导入模块执行此操作,但限制是它们都使用相同的记录器名称。模块的记录器以模块命名(使用 __name__),因此脚本可以使用顶级包名称找到并重新使用它。

所以一个脚本和一个导入的模块可以共享一个记录器。但是如何将第二个导入的模块附加到该记录器?如果我希望脚本的日志名称与导入模块的名称不同怎么办?这是我想出的解决方案。它有效,但它很丑陋。我需要 三个 个记录器对象!有没有更好的方法?

# Code for module maps.publish.upload
import logging
logger = logging.getLogger(__name__)
class ServicePublisher(object):
    def __init__(self):
        logger.debug(f'start constructor {__class__.__name__}')

Class charts.staffing.report.StaffingReport 等同于 ServicePublisher

# log_test.py script
import logging
from pathlib import Path
from charts.staffing.report import StaffingReport
from maps.publish.upload import ServicePublisher

filename = Path(__file__).parts[-1]
logger = logging.getLogger(filename)
logger.setLevel(logging.DEBUG)
fh = logging.FileHandler('log_test.log')
fh.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
fh.setFormatter(formatter)
logger.addHandler(fh)

logger_maps = logging.getLogger('maps')
logger_maps.setLevel(logging.DEBUG)
logger_maps.addHandler(fh)

logger_charts = logging.getLogger('charts')
logger_charts.setLevel(logging.DEBUG)
logger_charts.addHandler(fh)

logger.debug(f'start {filename}')
publisher = ServicePublisher()
report = StaffingReport()

这是日志输出,完全符合预期:

log_test.py - DEBUG - start log_test.py
maps.publish.upload - DEBUG - start constructor ServicePublisher
charts.staffing.report - DEBUG - start constructor StaffingReport

有没有办法在所有三种情况下使用我的 logger 对象,所以我不需要 logger_mapslogger_charts,同时在日志输出中仍然保持不同的名称?

我在这方面做了更多的工作,并没有真正想出更好的解决方案,但我会在下面分享我学到的东西。

1. 我尝试转到根管理器的记录器字典并为那里的所有内容创建一个记录器。您不需要在包结构中为 sub-modules 创建记录器。例如:charts.staffing.report 将有一个父记录器 charts.staffing,它有一个父记录器 charts,这就是您所需要的,所以我删除了其中带有 . 的所有内容。

logger_names = [name for name in logging.root.manager.loggerDict
                if '.' not in name]

然后,我对列表中的每个名称使用 logging.getLogger(<name>),并将我的处理程序附加到它。这非常简单,以至于我爱上了这个解决方案,直到我对它进行了更多测试。我正在使用调试级别的日志记录,这会打开我使用的某些 API 中每个导入包的日志记录。这些 API 还导入带有记录器的包,所以这真的很垃圾。默认情况下,我认为那些记录器都冒泡到警告级别的根记录器,这似乎更合适。因此,我的第一课是有意识地了解您要修改哪些记录器,而不是仅仅检查所有记录器。

2. 正如我上面提到的,Python 冒泡从 sub-modules 登录到父模块的记录器。如果让我重新来过,我会以组织名称前缀开头来命名我的所有包。例如,对于包 chartsmaps,如果我在 Acme Corporation 工作,那么我会将它们命名为 acme.chartsacme.maps 这样我就可以只创建一个记录器 acme 处理我所有的包裹。一旦你有大量代码使用你的包,这就更难了,所以当你开始时要考虑这一点。

请注意,约定是使用记录器的包名称,但名称只是一个字符串,您可以随意命名。我可以做这样的事情来添加一个统一所有这些的人工前缀:

logger = logging.getLogger(f"acme.{__name__}")

即使用户仍然将我的示例包导入为 chartsmaps,记录器将被命名为 acme.chartsacme.maps,您可以使用名为 acme 的单个记录器。这是一个很好的技术技巧,但我必须将其记录下来并传达给我的用户,因为包 charts 将使用名为 acme.

的记录器并不直观

3. 我仍在为我正在导入的每个自定义包创建一个记录器。我认为它不会对我的代码产生任何重大开销,但可能需要大规模考虑。在规模上,您不希望日志记录低于警告级别,这是默认情况下从根记录器获得的,因此这可能没有实际意义。

4. 为了简化我的代码,我编写了一个小函数,它接受包名列表和处理程序列表作为输入。它为每个名称获取一个记录器并附加处理程序。对于一两个额外的记录器,在您的代码中创建它们可能就像调用我的 def 一样容易。但是,如果您有一大堆要为其创建记录器的包,那么它真的会清理您的代码。