【问题标题】:How to extend the logging.Logger Class?如何扩展 logging.Logger 类?
【发布时间】:2016-09-14 14:02:58
【问题描述】:

我想从一个继承自 Python 的 logging.Logger 类的基本日志记录类开始。但是,我不确定应该如何构建我的类,以便建立自定义继承记录器所需的基础知识。

这是我目前在 logger.py 文件中的内容:

import sys
import logging
from logging import DEBUG, INFO, ERROR

class MyLogger(object):
    def __init__(self, name, format="%(asctime)s | %(levelname)s | %(message)s", level=INFO):
        # Initial construct.
        self.format = format
        self.level = level
        self.name = name

        # Logger configuration.
        self.console_formatter = logging.Formatter(self.format)
        self.console_logger = logging.StreamHandler(sys.stdout)
        self.console_logger.setFormatter(self.console_formatter)

        # Complete logging config.
        self.logger = logging.getLogger("myApp")
        self.logger.setLevel(self.level)
        self.logger.addHandler(self.console_logger)

    def info(self, msg, extra=None):
        self.logger.info(msg, extra=extra)

    def error(self, msg, extra=None):
        self.logger.error(msg, extra=extra)

    def debug(self, msg, extra=None):
        self.logger.debug(msg, extra=extra)

    def warn(self, msg, extra=None):
        self.logger.warn(msg, extra=extra)

这是主myApp.py

import entity
from core import MyLogger

my_logger = MyLogger("myApp")

def cmd():
    my_logger.info("Hello from %s!" % ("__CMD"))

entity.third_party()
entity.another_function()
cmd()

这是entity.py 模块:

# Local modules
from core import MyLogger

# Global modules
import logging
from logging import DEBUG, INFO, ERROR, CRITICAL

my_logger = MyLogger("myApp.entity", level=DEBUG)

def third_party():
    my_logger.info("Initial message from: %s!" % ("__THIRD_PARTY"))

def another_function():
    my_logger.warn("Message from: %s" % ("__ANOTHER_FUNCTION"))

当我运行主应用程序时,我得到了这个:

2016-09-14 12:40:50,445 | INFO | Initial message from: __THIRD_PARTY!
2016-09-14 12:40:50,445 | INFO | Initial message from: __THIRD_PARTY!
2016-09-14 12:40:50,445 | WARNING | Message from: __ANOTHER_FUNCTION
2016-09-14 12:40:50,445 | WARNING | Message from: __ANOTHER_FUNCTION
2016-09-14 12:40:50,445 | INFO | Hello from __CMD!
2016-09-14 12:40:50,445 | INFO | Hello from __CMD!

所有内容都打印了两次,可能是我未能正确设置记录器类。

更新 1

让我澄清一下我的目标。

(1) 我想将主要的日志记录功能封装在一个位置,这样我就可以做到这一点:

 from mylogger import MyLogger
 my_logger = MyLogger("myApp")
 my_logger.info("Hello from %s!" % ("__CMD"))

(2) 我打算使用CustomFormatterCustomAdapter 类。此位不需要自定义日志记录类,可以立即插入。

(3) 在底层logger类(记录等)的定制方面我大概不需要太深入,拦截logger.infologgin.debug等就足够了.

所以回顾一下在这些论坛上多次流传的this python receipt

我试图找到拥有Logger Class 之间的最佳点,但仍然能够使用内置函数,例如分配FormattersAdapters 等。所以一切都与logging 模块兼容.

class OurLogger(logging.getLoggerClass()):
    def makeRecord(self, name, level, fn, lno, msg, args, exc_info, func=None, extra=None):
        # Don't pass all makeRecord args to OurLogRecord bc it doesn't expect "extra"
        rv = OurLogRecord(name, level, fn, lno, msg, args, exc_info, func)
        # Handle the new extra parameter.
        # This if block was copied from Logger.makeRecord
        if extra:
            for key in extra:
                if (key in ["message", "asctime"]) or (key in rv.__dict__):
                    raise KeyError("Attempt to overwrite %r in LogRecord" % key)
                rv.__dict__[key] = extra[key]
        return rv

更新 2

我用一个简单的 python 应用程序创建了一个 repo,演示了一个可能的解决方案。不过,我很想改进这一点。

xlog_example

此示例有效地演示了通过继承覆盖logging.Logger 类和logging.LogRecord 类的技术。

两个外部项混合到日志流中:funcnameusername,没有使用任何 FormattersAdapters

【问题讨论】:

  • @JonasWielicki 我想封装基于现有设施的日志记录机制,我想使用自定义FormattersHandlersAdapters 等。这个想法是(作为练习)以覆盖所需的日志记录位。话虽如此,我知道这一直在社区中引起一定的争议。我仍然相信有一种方法可以使用自定义类等。我知道还有其他官方方法可以做到这一点。
  • 我创建了一个带有工作基本版本的仓库。请阅读UPDATE (02)了解更多信息。
  • 归根结底,logging 模块在定制方面提供了许多选项。但是,如果我们想为logging.debug()logging.info() 等函数添加更复杂的自定义逻辑,该怎么办?比方说,在现有的 level 逻辑之上。例如do not display any messages if the day is Monday。如果不劫持loggin.info() 方法并将我们的逻辑注入其中,这怎么可能?
  • @JonasWielicki 请不要说日志模块的创建者本人鼓励Logger 继承:“...... findCaller 应该在 Logger 子类中被覆盖以用于这些用例 - 不像 gonvaled ,我觉得这不是一个常见的用例。OOP 的一点是您可以通过子类化和覆盖来扩展现有功能以适应特殊情况。请注意,可能有其他方式(例如使用过滤器)来提供额外的处理,而不是使用包装器。”
  • 以上引用来自thisVinay Sajip 的评论

标签: python logging


【解决方案1】:

在这个阶段,我相信我迄今为止所做的研究以及旨在总结解决方案的示例足以回答我的问题。通常,有许多方法可用于包装日志记录解决方案。这个特定问题旨在关注利用logging.Logger 类继承的解决方案,以便可以更改内部机制,但其余功能保持不变,因为它将由原始logging.Logger 类提供。

话虽如此,在使用类继承技术时应该非常小心。日志记录模块提供的许多功能已经足以维持和运行稳定的日志记录工作流程。当目标是对日志数据的处理和导出方式进行某种根本性的改变时,从 logging.Logger 类继承可能很好。

总结一下,我发现有两种方法可以封装日志记录功能:

1) 传统日志记录:

这只是使用提供的日志记录方法和函数,但将它们包装在一个模块中,以便将一些通用的重复性任务组织在一个地方。这样一来,日志文件、日志级别、管理自定义FiltersAdapters 等就变得简单了。

我不确定在这种情况下是否可以使用class 方法(我不是在谈论作为第二项主题的超类方法),因为看起来事情变得越来越复杂当日志记录调用包装在一个类中时。我想听听这个问题,我一定会准备一个探讨这个方面的问题。

2) Logger 继承:

这种方法基于从原始logging.Logger类继承并添加到现有方法或通过修改内部行为完全劫持它们。机制基于以下代码:

# Register our logger.
logging.setLoggerClass(OurLogger)
my_logger = logging.getLogger("main")

从现在开始,我们依靠自己的 Logger,但我们仍然能够从所有其他日志记录工具中受益:

# We still need a loggin handler.
ch = logging.StreamHandler()
my_logger.addHandler(ch)

# Confgure a formatter.
formatter = logging.Formatter('LOGGER:%(name)12s - %(levelname)7s - <%(filename)s:%(username)s:%(funcname)s> %(message)s')
ch.setFormatter(formatter)

# Example main message.
my_logger.setLevel(DEBUG)
my_logger.warn("Hi mom!")

此示例至关重要,因为它演示了在不使用自定义 AdaptersFormatters 的情况下注入两个数据位 usernamefuncname

有关此解决方案的更多信息,请参阅xlog.py repo。这是我根据other questions 和其他sources 的一些代码准备的示例。

【讨论】:

    【解决方案2】:

    这一行

    self.logger = logging.getLogger("myApp")
    

    总是检索对同一个记录器的引用,因此每次实例化MyLogger 时都会向它添加一个额外的处理程序。以下将修复您当前的实例,因为您两次都使用不同的参数调用 MyLogger

    self.logger = logging.getLogger(name)
    

    但请注意,如果您多次传递相同的name 参数,您仍然会遇到同样的问题。

    你的班级需要做的是跟踪它已经配置了哪些记录器。

    class MyLogger(object):
        loggers = set()
        def __init__(self, name, format="%(asctime)s | %(levelname)s | %(message)s", level=INFO):
            # Initial construct.
            self.format = format
            self.level = level
            self.name = name
    
            # Logger configuration.
            self.console_formatter = logging.Formatter(self.format)
            self.console_logger = logging.StreamHandler(sys.stdout)
            self.console_logger.setFormatter(self.console_formatter)
    
            # Complete logging config.
            self.logger = logging.getLogger(name)
            if name not in self.loggers:
                self.loggers.add(name)
                self.logger.setLevel(self.level)
                self.logger.addHandler(self.console_logger)
    

    这根本不允许您重新配置记录器,但我将其留作练习以弄清楚如何正确执行此操作。

    但要注意的关键是,您不能拥有两个单独配置的具有相同名称的记录器。


    当然,logging.getLogger 总是返回对给定名称的同一对象的引用这一事实意味着您的类与logging 模块的工作不一致。只需在程序启动时配置一次记录器,然后根据需要使用getLogger 获取参考。

    【讨论】:

    • 为什么它不允许我配置日志记录?我的目标是创建一个日志类,其行为类似于logging.Logger 对象,但是在内部封装了不同的自定义选项。我知道有很多项目和帖子解释了这一点。我已经研究了一段时间。不幸的是,信息分散且难以理解。出于这个原因,我试图提出最基本的解决方案,为这一概念奠定基础,并允许在定制方面进行进一步的工作。
    • 在最基本的层面上,您无法重新配置记录器,因为我将配置调用放在了 if 语句中,该语句仅在您第一次尝试配置特定记录器时运行。对于处理程序之类的东西,您不是在设置 处理程序,而是在 添加 一个新处理程序,这些会累积。
    猜你喜欢
    • 2020-06-22
    • 2013-03-30
    • 2016-05-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多