如何为自己的日志记录级别编写自己的日志记录方法

如何为自己的日志记录级别编写自己的日志记录方法

问题描述:


我想用自己的方法扩展记录器(由logging.getLogger("rrcheck")): def warnpfx(...):

Hi
I would like to extend my logger (taken by logging.getLogger("rrcheck")) with my own methods like: def warnpfx(...):

如何做到最好?

我最初的愿望是让root logger将所有内容写入文件,并另外将logger("rrcheck")写入stdout,但是后者还应该具有一些其他方法和级别.我需要它在某些消息前面加上!PFXWRN"前缀(但只有那些要发送到stdout的消息),而其他消息保持不变.我还想分别设置root和命名logger的日志记录级别.

My original wish is to have a root logger writing everything to a file and additionally named logger ("rrcheck") writing to stdout, but the latter should also have some other methods and levels. I need it to prepend some messages with "! PFXWRN" prefix (but only those going to stdout) and to leave other messages unchanged. I would like also to set logging level separately for root and for named logger.

这是我的代码:

class CheloExtendedLogger(logging.Logger):
    """
    Custom logger class with additional levels and methods
    """
    WARNPFX = logging.WARNING+1

    def __init__(self, name):
        logging.Logger.__init__(self, name, logging.DEBUG)                

        logging.addLevelName(self.WARNPFX, 'WARNING')

        console = logging.StreamHandler()
        console.setLevel(logging.DEBUG)
        # create formatter and add it to the handlers
        formatter = logging.Formatter("%(asctime)s [%(funcName)s: %(filename)s,%(lineno)d] %(message)s")
        console.setFormatter(formatter)

        # add the handlers to logger
        self.addHandler(console)

        return

    def warnpfx(self, msg, *args, **kw):
        self.log(self.WARNPFX, "! PFXWRN %s" % msg, *args, **kw)


logging.setLoggerClass(CheloExtendedLogger)    
rrclogger = logging.getLogger("rrcheck")
rrclogger.setLevel(logging.INFO)

def test():
    rrclogger.debug("DEBUG message")
    rrclogger.info("INFO message")
    rrclogger.warnpfx("warning with prefix")

test()

这是一个输出-函数和lilne编号错误: warnpfx 而不是 test

And this is an output - function and lilne number is wrong: warnpfx instead of test

2011-02-10 14:36:51,482 [test: log4.py,35] INFO message
2011-02-10 14:36:51,497 [warnpfx: log4.py,26] ! PFXWRN warning with prefix

也许我自己的记录器方法不是最好的方法?
您建议走哪个方向(自己的记录器,自己的处理程序,自己的格式化程序等)?

Maybe my own logger approach is not the best one?
Which direction would you propose to go (own logger, own handler, own formatter, etc.)?

如果我想要另一个记录器,该如何进行?
不幸的是,日志记录无法注册自己的记录器,因此getLogger(name)将需要一个必需的记录器.

How to proceed if I would like to have yet another logger?
Unfortunatelly logging has no possibility to register an own logger, so then getLogger(name) would take a required one...

关于,
兹比格涅夫

Regards,
Zbigniew

如果您选中

If you check Python sources, you'll see that the culprit is the Logger.findCaller method that walks through the call stack and searches for a first line that is not in the logging.py file. Because of this, your custom call to self.log in CheloExtendedLogger.warnpfx registers a wrong line.

不幸的是,logging.py中的代码不是非常模块化,因此修复起来很丑陋:您必须自己在子类中重新定义findCaller方法,以便它同时考虑到logging.py文件和记录器所在的文件(请注意,文件中除记录器外不应有任何其他代码,否则结果将不准确).这需要在方法主体中进行单行更改:

Unfortunately, the code in logging.py is not very modular, so the fix is rather ugly: you have to redefine the findCaller method yourself in your subclass, so that it takes into account both the logging.py file and the file in which your logger resides (note that there shouldn't be any code other than the logger in your file, or again the results will be inaccurate). This requires a one-line change in the method body:

class CheloExtendedLogger(logging.Logger):

    [...]

    def findCaller(self):
        """
        Find the stack frame of the caller so that we can note the source
        file name, line number and function name.
        """
        f = logging.currentframe().f_back
        rv = "(unknown file)", 0, "(unknown function)"
        while hasattr(f, "f_code"):
            co = f.f_code
            filename = os.path.normcase(co.co_filename)
            if filename in (_srcfile, logging._srcfile): # This line is modified.
                f = f.f_back
                continue
            rv = (filename, f.f_lineno, co.co_name)
            break
        return rv

为此,您需要在文件中定义自己的_srcfile变量.同样,logging.py不使用函数,而是将所有代码置于模块级别,因此您必须再次复制粘贴:

For this to work, you need to define your own _srcfile variable in your file. Again, logging.py doesn't use a function, but rather puts all the code on the module level, so you have to copy-paste again:

if hasattr(sys, 'frozen'): #support for py2exe
    _srcfile = "logging%s__init__%s" % (os.sep, __file__[-4:])
elif string.lower(__file__[-4:]) in ['.pyc', '.pyo']:
    _srcfile = __file__[:-4] + '.py'
else:
    _srcfile = __file__
_srcfile = os.path.normcase(_srcfile)

好吧,也许如果您不关心编译版本,那么最后两行就足够了.

Well, maybe if you don't care for compiled versions, two last lines will suffice.

现在,您的代码可以按预期工作了:

Now, your code works as expected:

2011-02-10 16:41:48,108 [test: lg.py,16] INFO message
2011-02-10 16:41:48,171 [test: lg.py,17] ! PFXWRN warning with prefix

对于多个记录器类,如果您不介意记录器名称和记录器类之间的依赖关系,则可以创建logging.Logger的子类,该子类将基于其记录的调用委派给适当的记录器类名字是.可能还有其他更优雅的可能性,但我现在想不起来.

As for multiple logger classes, if you don't mind the dependency between a logger name and a logger class, you could make a subclass of logging.Logger that would delegate its calls to an appropriate logger class, based on what its name was. There are probably other, more elegant possibilities, but I can't think of any right now.