我试图通过首先创建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 层次结构
看起来每次我使用 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
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
。
此外,您将同一个文件打开三次;这可能会导致覆盖或交错输出的问题。