首页 > 解决方案 > python记录父子层次结构

问题描述

我试图通过首先创建一个 Father.Child 日志然后使用 logging.getLogger() 重新调用父亲日志来创建一个父/子日志记录层次结构,但由于某种原因我无法让它正常工作。下面是代码示例。在实际项目中,会有很多类将使用“clsLogger”创建一个 self.logger,每个类都会将所有类的日志写入同一个日志文件。

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 层次结构

标签: pythonlogginghierarchy

解决方案


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

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

多次调用具有相同名称的 getLogger() 将始终返回对同一 Logger 对象的引用。

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

<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类时,都会创建相同的处理程序并将它们附加到可能已经存在的记录器上。


分解它,这是:

log1 = clsLogger('Father.Child')

创建一个名为的记录器并将一个和一个Father.Child附加到它。然后这个:FileHandlerStreamHandler

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')

将消息发送到记录器的两个StreamHandlerFileHandler实例中的每一个Father.Child,另外,由于Father是父级Father.Child并且您没有显式禁用传播,还将向其发送它的日志记录发送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

但是,您的课程几乎是样板代码。正如文档所述(强调我的):

注意:如果将处理程序附加到记录器及其一个或多个祖先,它可能会多次发出相同的记录。通常,您不需要将处理程序附加到多个记录器- 如果您只是将其附加到记录器层次结构中最高的适当记录器,那么它将看到所有后代记录器记录的所有事件,前提是它们的传播设置保留为 True。一个常见的场景是仅将处理程序附加到根记录器,并让传播处理其余部分。

因此,使用隐式根记录器,以下将实现完全相同的目标,但在层次结构中或旁边使用附加记录器为您提供更大的灵活性:

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

推荐阅读