python 记录父子层次结构

问题描述 投票:0回答:2

我试图通过首先创建Father.Child日志,然后使用logging.getLogger()重新调用Father日志来创建Father/Child日志记录层次结构,但由于某种原因,我无法让它正常工作。 下面是代码示例。在实际项目中,会有很多类,它们将使用“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 层次结构

python logging hierarchy
2个回答
5
投票

看起来每次我使用 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
的记录器,并向其附加一个
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

现有 
记录器,并将另一个
FileHandler
StreamHandler
附加到其上。所以你的最后一行:

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

将消息发送到

StreamHandler
记录器的两个
FileHandler
Father.Child
实例中的每一个,另外,由于
Father
Father.Child
的父级,并且您没有显式禁用 传播,因此还会发送将记录记录到
Father
,然后将其发送到自己的
StreamHandler
FileHandler
。这就是为什么你会得到三次输出。

这可以像这样变得可见:

<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

0
投票
log1 = clsLogger('Father.Child') # <-- creates logger, attached 2 handlers
log1 = clsLogger('Father') # <-- creates logger, attaches 2 handlers
# ^^ WILL ALSO HANDLE logs sent to Father.Child
log1.logger.info('log from father')
log1 = clsLogger('Father.Child') # <-- retrieves first logger, ADDS 2 NEW HANDLERS!
log1.logger.info('log from child')

因此,不要为同一个日志名称调用 fn 两次,或者添加另一对处理程序并因此创建多个输出。

如果您不希望发送到 Child 的日志向上渗透到 Father(然后渗透到 root),则设置

Child.propagate = False

此外,您将同一个文件打开三次;这可能会导致覆盖或交错输出的问题。

© www.soinside.com 2019 - 2024. All rights reserved.