【问题标题】:Python Logging with a common logger class mixin and class inheritance具有通用记录器类 mixin 和类继承的 Python 日志记录
【发布时间】:2020-04-25 05:48:51
【问题描述】:

我想创建一个 Python 日志记录类,它可以作为一种常用的日志记录配置方式被继承,但可以从父类中单独控制基类的日志记录级别。这类似于How to use python logging in multiple modules。 Vinay Sajip 的answer 使用 LogMixin 非常接近。以下是我稍作修改的版本。

我的大多数类都继承了较小的类。例如:

文件名:LogMixin.py

import logging, logging.config
import yaml
class LogMixin(object):
    __loggerConfigured = False
    @property
    def logger(self):
        if not self.__loggerConfigured:
            with open('log_config.yaml', 'rt') as f:
                config = yaml.load(f.read())
                logging.config.dictConfig(config)
            self.__loggerConfigured = True
        name = '.'.join([self.__class__.__name__])
        return logging.getLogger(name)

文件名:Base.py

from LogMixin import LogMixin
class Base(LogMixin):
    def __init__(self):
        self.logger.debug("Debug Base")
    def run_base(self):
        self.logger.debug("Debug Running Base")
        self.logger.info("Info Running Base")
if __name__ == '__main__':
    my_base = Base()
    my_base.run_base()         

文件名:Parent.py

from Base import Base
class Parent(Base):
    def __init__(self):
        self.logger.debug("Debug Parent")
    def run_parent(self):
        self.logger.debug("Debug Running Parent")
        self.logger.info("Info Running Parent")

if __name__ == '__main__':
    my_parent = Parent()
    my_parent.run_base()
    my_parent.run_parent()

文件名:log_config.yaml

---
version: 1
disable_existing_loggers: False

# Configuring the default (root) logger is highly recommended
root:
    level: WARNING
    handlers: [console]

# Configuration for logger set with logging.getLogger(NAME)
loggers:
    Base:
        level: INFO
        handlers: [console]
        propagate: no
    Parent:
        level: DEBUG
        handlers: [console]
        propagate: no

formatters:
    simple:
        format: "%(asctime)s - %(name)s - %(levelname)s - %(message)s"

handlers:
    console:
        class: logging.StreamHandler
        formatter: simple
        stream: ext://sys.stdout
...

我得到了通用日志记录配置的好处。但是,我希望独立控制 Base 和 Parent 的日志级别。使用上面的配置文件,我得到:

$ python Base.py                 
2015-03-16 00:06:23,716 - Base - INFO - Info Running Base
$ python Parent.py                
2015-03-16 00:06:19,682 - Parent - DEBUG - Debug Parent
2015-03-16 00:06:19,682 - Parent - DEBUG - Debug Running Base
2015-03-16 00:06:19,682 - Parent - INFO - Info Running Base
2015-03-16 00:06:19,682 - Parent - DEBUG - Debug Running Parent
2015-03-16 00:06:19,682 - Parent - INFO - Info Running Parent

我明白我为什么会得到这个,我只有一个记录器“父母”。但是,总的来说,我宁愿得到以下内容:

$ python Base.py                 
2015-03-16 00:06:23,716 - Base - INFO - Info Running Base
$ python Parent.py                
2015-03-16 00:06:19,682 - Parent - DEBUG - Debug Parent
2015-03-16 00:06:19,682 - Base - INFO - Info Running Base
2015-03-16 00:06:19,682 - Parent - DEBUG - Debug Running Parent
2015-03-16 00:06:19,682 - Parent - INFO - Info Running Parent

(注意没有与 Base.py 相关的 DEBUG)。
甚至更好:

$ python Base.py                 
2015-03-16 00:06:23,716 - Base - INFO - Info Running Base
$ python Parent.py                
2015-03-16 00:06:19,682 - Parent - DEBUG - Debug Parent
2015-03-16 00:06:19,682 - Parent.Base - INFO - Info Running Base
2015-03-16 00:06:19,682 - Parent - DEBUG - Debug Running Parent
2015-03-16 00:06:19,682 - Parent - INFO - Info Running Parent

(注意名称是 Parent.Base,所以我可以看到继承。) 这可以通过一个简单的 LogMixin 类实现吗?

【问题讨论】:

    标签: python inheritance logging


    【解决方案1】:

    metaclass 会更合适。当一个类被定义时,它将获得自己的记录器。名称修改确保每个类都使用自己的记录器。

    import logging
    import sys
    
    logging.basicConfig(stream=sys.stdout)
    
    class MetaBase(type):
        def __init__(cls, *args):
            super().__init__(*args)
    
            # Explicit name mangling
            logger_attribute_name = '_' + cls.__name__ + '__logger'
    
            # Logger name derived accounting for inheritance for the bonus marks
            logger_name = '.'.join([c.__name__ for c in cls.mro()[-2::-1]])
    
            setattr(cls, logger_attribute_name, logging.getLogger(logger_name))
    
    class Base(metaclass=MetaBase):
        def __init__(self):
            self.__logger.error('init base')
    
        def func_base(self):
            self.__logger.error('func base')
    
    class Parent(Base):
        def func_parent(self):
            self.__logger.error('func parent')
    
    p = Parent()
    p.func_base()
    p.func_parent()
    

    结果:

    ERROR:Base:init base
    ERROR:Base:func base
    ERROR:Base.Parent:func parent
    

    这种方法比混合有一些额外的好处。

    • 每个类的记录器是在类定义中创建的,并通过直接属性引用进行访问。避免财产和getLogger 电话
    • 子类只需要继承base,不用记得加MixIn

    我已简化示例以演示关键概念。应该跨文件和配置文件工作。

    【讨论】:

    • 这是一个非常优雅的解决方案,感谢您提供代码。
    • 这是一个非常优雅的解决方案,感谢您提供代码。我有一个小问题,我似乎无法解决。我有几个使用“Base”类的类。这具有多个具有相同记录器名称的类的不幸副作用。我尝试在元类中提取父类名称,并尝试在初始化后在“Base”类中设置记录器名称。似乎都不起作用
    • @scientificCompNoob 我知道您希望每个记录器都有一个唯一的名称,但类名在多个模块中不是唯一的。在这种情况下,请以适合您口味的方式在记录器名称中包含 cls.__module__。如果您想多一点,我注意到所有记录器名称都存储在 logging.Logger.manager.loggerDict 中,您可以先查看那里,只有在发生冲突时才扩展名称。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多