如何扩展logging.Logger类?

2024-09-29 21:45:30 发布

您现在位置:Python中文网/ 问答频道 /正文

我想从一个基本的日志类开始,它继承自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!

所有内容都打印了两次,可能是因为我未能正确设置logger类。

--更新(01):澄清我的目标--

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

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

(2)我计划使用CustomFormatterCustomAdapter类。此位不需要自定义日志类,可以直接插入。

(3)我可能不需要深入了解底层日志类(记录等)的定制,截取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

--更新(02):正在进行的工作可能的解决方案--

我用一个简单的python应用程序创建了一个repo,演示了一个可能的解决方案。请随时登峰造极,帮助我改进。

xlog_example

这个例子有效地演示了通过继承重写logging.Logger类和logging.LogRecord类的技术。

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


Tags: keyfromimportselfinfononemylogging
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类继承的解决方案,以便可以更改内部机制,而其余的功能保持原样,因为它将由原始logging.Logger类提供。

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

总结一下,我发现有两种包装日志功能的方法:

1)传统的日志记录:

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

我不确定在这个场景中是否可以使用class方法(我不是在讨论第二个项目的主题是一个超级类方法),因为当日志调用包装在一个类中时,事情似乎变得越来越复杂。我想听听这个问题,我一定会准备一个问题,探讨这个方面。

2)日志继承:

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

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

从现在起,我们依靠我们自己的记录器,但我们仍然能够从所有其他日志设施中受益:

# 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的代码位准备的示例。

相关问题 更多 >

    热门问题