如何扩展logging.Logger类?

时间:2016-09-14 14:02:59

标签: python logging

我想从一个继承自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!

所有内容都打印了两次,因为我可能无法正确设置记录器类。

--- 更新(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

2 个答案:

答案 0 :(得分:8)

在这个阶段,我相信到目前为止我所做的研究以及提供解决方案的意图的例子足以作为我问题的答案。通常,有许多方法可用于包装日志记录解决方案。这个特定的问题旨在关注利用logging.Logger类继承的解决方案,以便可以改变内部机制,但其余的功能保持原样,因为它将由原始{{1 }。class。

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

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

1)传统记录:

这只是使用提供的日志记录方法和函数,但将它们包装在一个模块中,以便将一些通用的重复性任务组织在一个地方。通过这种方式,日志文件,日志级别,管理自定义logging.LoggerFilters等内容将非常简单。

我不确定在这种情况下是否可以使用Adapters方法(我不是在讨论第二项主题的超级方法),因为它似乎是当日志记录调用包含在类中时变得复杂。我想知道这个问题,我肯定会准备一个探讨这方面的问题。

2)记录器继承:

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

logging.Logger

从现在开始,我们依靠自己的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!") username的情况下注入两个数据位funcnameAdapters

有关此解决方案的详细信息,请参阅xlog.py repo。这是我根据other questions和其他sources的代码编写的示例。

答案 1 :(得分:3)

这一行

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获取参考。