2016-09-14 106 views
2

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

这是我在我的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! 

一切都打印两次,因为可能我没有正确设置记录器类。

--- UPDATE(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 

--- UPDATE(02):一个工作正在进行中可能的解决方案 ---

我已经创建了一个简单的Python应用程序展示了一个可能的解决方案回购。请随时登顶,帮助我改进。

xlog_example

该实施例有效地演示重写logging.Logger类和logging.LogRecord类通过继承的技术。

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

+0

你为什么要那样做? –

+0

@JonasWielicki我想封装基于现有设施的日志记录机制,我想使用自定义的'Formatters','Handlers','Adapters'等。这个想法是(作为练习)来覆盖所需的位的日志记录。话虽如此,我知道这一直在社区引起一定的争议。我仍然相信有一种方法可以通过自定义类来实现。我知道还有其他**官方的方法。 – symbolix

+0

我创建了一个具有正常工作基本版本的回购协议。请阅读“UPDATE(02)”以获取更多信息。 – symbolix

回答

4

在这个阶段,我认为到目前为止我所做的研究以及提供了解决方案的意图的例子足以作为我的问题的答案。通常,可以使用许多方法来包装日志记录解决方案。这个特定的问题旨在集中讨论一种利用类继承的解决方案,以便内部机制可以改变,但其余的功能保持原样,因为它将由原始的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的代码位准备的一个示例。

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

+0

为什么它不允许我配置日志记录?我的目标是创建一个类似于logging.Logger对象的日志记录类,但是在内部封装了不同的自定义选项。我知道有很多项目和文章解释这一点。我一直在研究这一点。不幸的是,这些信息很分散,很难理解。出于这个原因,我试图提出最基本的解决方案,这将提供这个概念的基础,并允许在定制方面进行进一步的工作。 – symbolix

+0

在最基本的层次上,您不能重新配置记录器,因为我将配置调用置于只在第一次尝试配置特定记录器时运行的'if'语句。对于像处理程序这样的事情,你不设置*处理程序,你*添加*一个新的处理程序,并且这些处理程序会累积。 – chepner