python 记录父子层次结构

python logging Father Child hierarchy

我试图通过首先创建一个 Father.Child 日志然后使用 logging.getLogger() 重新调用父日志来创建一个 Father/Child 日志层次结构,但由于某种原因我无法获得它才能正常工作。 下面是代码示例。在实际项目中会有很多 classes 会使用 'clsLogger' 创建一个 self.logger 并且每个 class 都会将所有 [=] 的日志写入同一个日志文件29=]es.

import logging
class clsLogger():

def __init__(self,LoggerName,Child=False,LoggerFileName='QpythonLog.txt'):
    #create logger :
    self.logger = logging.getLogger(LoggerName)
    self.lvl = logging.DEBUG
    self.logger.setLevel(self.lvl)
    formatter = logging.Formatter('%(asctime)s  %(name)s  %(filename)s  %(levelname)s: %(message)s')

    #log to file :
    self.filehandler = logging.FileHandler(LoggerFileName)
    self.filehandler.setLevel = self.lvl
    self.logger.addHandler(self.filehandler)
    self.filehandler.setFormatter(formatter)

    #log to console :
    self.consoleHandler = logging.StreamHandler()
    self.consoleHandler.setLevel(self.lvl)
    self.logger.addHandler(self.consoleHandler)
    self.consoleHandler.setFormatter(formatter)


log1 = clsLogger('Father.Child')
log1 = clsLogger('Father')
log1.logger.info('log from father')
log1 = clsLogger('Father.Child')
log1.logger.info('log from child')

输出(错误的)是:

2020-06-26 00:36:11,727  Father  Services_TalynM_TalynA_v2.py  INFO: log from father
2020-06-26 00:36:11,819  Father.Child  Services_TalynM_TalynA_v2.py  INFO: log from child
2020-06-26 00:36:11,819  Father.Child  Services_TalynM_TalynA_v2.py  INFO: log from child
2020-06-26 00:36:11,819  Father.Child  Services_TalynM_TalynA_v2.py  INFO: log from child

我真正想要的是:

2020-06-26 00:36:11,727  Father  Services_TalynM_TalynA_v2.py  INFO: log from father
2020-06-26 00:36:11,819  Father.Child  Services_TalynM_TalynA_v2.py  INFO: log from child

看起来每次我使用 logger.getLogger 它都会创建一个新的记录器对象,而不是使用第一个创建的 Father.Child 层次结构

It looks like every time I use logger.getLogger it creates a new logger object, instead of using the first created Father.Child hierarchy

当然不是。日志记录模块在内部字典 (logging.Logger.manager.loggerDict) 中注册每个记录器及其名称。根据定义,每个给定名称只能有一个记录器。根据 logging documentation:

Multiple calls to getLogger() with the same name will always return a reference to the same Logger object.

您可以在您的代码中验证这一点,如下所示:

<your code here>

# This is accessing an undocumented member; not safe for production code
print(logging.Logger.manager.loggerDict)

# Output:
# {'Father.Child': <Logger Father.Child (DEBUG)>, 'Father': <Logger Father (DEBUG)>}

问题是,每次实例化 clsLogger class 时,您都会创建相同的处理程序并将它们附加到可能已经存在的记录器。


分解一下,这个:

log1 = clsLogger('Father.Child')

创建一个名为 Father.Child 的记录器并附加一个 FileHandler 和一个 StreamHandler 到它。那么这个:

log1 = clsLogger('Father')

创建一个名为 Father 的记录器,根据名称成为 Father.Child 的父级并为其附加相同的处理程序。
下一行:

log1.logger.info('log from father')

Father 记录器发送消息,记录器将其发送给它的两个处理程序,因此行:

2020-06-26 00:36:11,727  Father  Services_TalynM_TalynA_v2.py  INFO: log from father

在控制台和文件中。这一行:

log1 = clsLogger('Father.Child')

获取名为 Father.Child 现有 记录器,并将另一个 FileHandlerStreamHandler 附加到它。所以你的最后一行:

log1.logger.info('log from child')

将消息发送到 Father.Child 记录器的两个 StreamHandlerFileHandler 实例中的每一个,另外,因为 Father 是 [=25= 的父级] 并且您没有明确禁用 propagation,还将日志记录发送到 Father,后者将其发送到它自己的 StreamHandlerFileHandler。这就是你得到三次输出的原因。

这可以像这样显示:

<your code here>

for lname, logger in logging.Logger.manager.loggerDict.items():
    print(lname, logger.handlers, logger.parent)

# Output
# Father.Child [<FileHandler /home/shmee/.PyCharmCE2020.1/scratches/QpythonLog.txt (NOTSET)>, <StreamHandler <stderr> (DEBUG)>, <FileHandler /home/shmee/.PyCharmCE2020.1/scratches/QpythonLog.txt (NOTSET)>, <StreamHandler <stderr> (DEBUG)>] <Logger Father (DEBUG)>
# Father [<FileHandler /home/shmee/.PyCharmCE2020.1/scratches/QpythonLog.txt (NOTSET)>, <StreamHandler <stderr> (DEBUG)>] <RootLogger root (WARNING)>

顺便说一句:您为 FileHandler 设置的级别不正确:

self.filehandler.setLevel = self.lvl

因此这些处理程序的级别 NOTSET。对于 StreamHandler:

你做的是正确的
self.consoleHandler.setLevel(self.lvl)

要实现你想要的,你基本上会做:

class clsLogger():

def __init__(self,LoggerName,Child=False,LoggerFileName='QpythonLog.txt'):
    #create logger :
    self.logger = logging.getLogger(LoggerName)
    self.lvl = logging.DEBUG
    self.logger.setLevel(self.lvl)
    formatter = logging.Formatter('%(asctime)s  %(name)s  %(filename)s  %(levelname)s: %(message)s')

    #log to file :
    self.filehandler = logging.FileHandler(LoggerFileName)
    self.filehandler.setLevel(self.lvl)
    self.logger.addHandler(self.filehandler)
    self.filehandler.setFormatter(formatter)

    #log to console :
    self.consoleHandler = logging.StreamHandler()
    self.consoleHandler.setLevel(self.lvl)
    self.logger.addHandler(self.consoleHandler)
    self.consoleHandler.setFormatter(formatter)

    self.logger.propagate = not Child


clsLogger('Father.Child', True)
clsLogger('Father')
logging.getLogger('Father').info('log from father')
logging.getLogger('Father.Child').info('log from child')

# Output
# 2020-06-26 00:36:11,727  Father  Services_TalynM_TalynA_v2.py  INFO: log from father
# 2020-06-26 00:36:11,819  Father.Child  Services_TalynM_TalynA_v2.py  INFO: log from child

但是,您的 class 几乎是样板代码。正如文档状态(强调我的):

Note: If you attach a handler to a logger and one or more of its ancestors, it may emit the same record multiple times. In general, you should not need to attach a handler to more than one logger - if you just attach it to the appropriate logger which is highest in the logger hierarchy, then it will see all events logged by all descendant loggers, provided that their propagate setting is left set to True. A common scenario is to attach handlers only to the root logger, and to let propagation take care of the rest.

因此,使用隐式根记录器,以下将实现完全相同的事情,但通过在层次结构中或旁边添加额外的记录器,您可以更加灵活:

logger = logging.getLogger()
handlers = [logging.FileHandler('QpythonLog.txt'), logging.StreamHandler()]
formatter = logging.Formatter('%(asctime)s  %(name)s  %(filename)s  %(levelname)s: %(message)s')
[h.setFormatter(formatter) for h in handlers]
[logger.addHandler(h) for h in handlers]
[e.setLevel(logging.DEBUG) for e in (logger, *handlers)]

logging.getLogger('Father').info('log from father')
logging.getLogger('Father.Child').info('log from child')
logging.getLogger('Father.Child.GrandChild').info('log from grandchild')
logging.getLogger('sthElse').info('log from something else')

# Output
# 2020-06-26 01:45:37,617  Father  frek.py  INFO: log from father
# 2020-06-26 01:45:37,617  Father.Child  frek.py  INFO: log from child
# 2020-06-26 01:45:37,617  Father.Child.GrandChild  frek.py  INFO: log from grandchild
# 2020-06-26 01:45:37,617  sthElse  frek.py  INFO: log from something else