在自定义类中包装记录器功能时显示正确的funcName

dan*_*ast 23 python logging

这是我用于记录的格式化字符串:

'%(asctime)s - %(levelname)-10s - %(funcName)s - %(message)s'
Run Code Online (Sandbox Code Playgroud)

但为了显示日志消息,我有一个包装器做了更多(我设置不同的日志级别,配置不同的日志后端,提供访问自定义级别的便利功能,等):

class MyLogger(logging.Logger):

    def split_line(self, level, message):
        ....
        self.log.(level, line)

    def progress(self, message):
        self.split_line(PROGRESS, message)
Run Code Online (Sandbox Code Playgroud)

使用此设置,每当我记录一些东西:

def myfunc():
    log.progress('Hello')
Run Code Online (Sandbox Code Playgroud)

我明白了:

013-10-27 08:47:30,130 - PROGRESS   - split_line - Hello
Run Code Online (Sandbox Code Playgroud)

这不是我想要的,即:

013-10-27 08:47:30,130 - PROGRESS   - myfunc     - Hello
Run Code Online (Sandbox Code Playgroud)

如何告诉记录器使用正确的上下文作为函数名?我认为这在堆栈框架上实际上会高出两个级别.

编辑

这是一个显示问题的测试程序:

import sys
import logging

PROGRESS = 1000

class MyLogger(logging.Logger):

    PROGRESS = PROGRESS
    LOG_FORMATTER = '%(asctime)s - %(levelname)-10s - %(funcName)s - %(message)s'
    DEF_LOGGING_LEVEL = logging.WARNING

    def __init__(self, log_name, level=None):
        logging.Logger.__init__(self, log_name)
        self.formatter = logging.Formatter(self.LOG_FORMATTER)
        self.initLogger(level)

    def initLogger(self, level=None):
        self.setLevel(level or self.DEF_LOGGING_LEVEL)
        self.propagate = False

    def add_handler(self, log_file, use_syslog):
        if use_syslog : hdlr = logging.handlers.SysLogHandler(address='/dev/log')
        elif log_file : hdlr = logging.FileHandler(log_file)
        else          : hdlr = logging.StreamHandler(sys.stderr)
        hdlr.setFormatter(self.formatter)
        self.addHandler(hdlr)
        return hdlr

    def addHandlers(self, log_file=None, progress_file=None, use_syslog=False):
        self.logger_hdlr = self.add_handler(log_file, use_syslog)
        if progress_file:
            self.progress_hdlr = self.add_handler(progress_file, use_syslog)
            self.progress_hdlr.setLevel(self.PROGRESS)
        else:
            self.progress_hdlr = None

    def split_line(self, level, txt, *args):
        txt = txt % (args)
        for line in txt.split('\n'):
            self.log(level, line)

    def progress(self, txt, *args):
        self.split_line(self.PROGRESS, txt, *args)

logging.setLoggerClass(MyLogger)
logging.addLevelName(PROGRESS, 'PROGRESS')
logger = logging.getLogger(__name__)
logger.addHandlers()

name = 'John'
logger.progress('Hello %s\nHow are you doing?', name)
Run Code Online (Sandbox Code Playgroud)

生产:

2013-10-27 09:47:39,577 - PROGRESS   - split_line - Hello John
2013-10-27 09:47:39,577 - PROGRESS   - split_line - How are you doing?
Run Code Online (Sandbox Code Playgroud)

glg*_*lgl 6

本质上,问题的代码在于类中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 = currentframe()
    #On some versions of IronPython, currentframe() returns None if
    #IronPython isn't run with -X:Frames.
    if f is not None:
        f = f.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 == _srcfile:
            f = f.f_back
            continue
        rv = (co.co_filename, f.f_lineno, co.co_name)
        break
    return rv
Run Code Online (Sandbox Code Playgroud)

返回调用者链中不属于当前模块的第一个函数。

您可以Logger通过添加稍微复杂的逻辑来子类化并重写此方法。跳过另一个调用深度级别或添加另一个条件。


在您非常特殊的情况下,避免自动线路分割并执行以下操作可能会更简单

logger.progress('Hello %s', name)
logger.progress('How are you doing?')
Run Code Online (Sandbox Code Playgroud)

或做

def splitter(txt, *args)
    txt = txt % (args)
    for line in txt.split('\n'):
        yield line

for line in splitter('Hello %s\nHow are you doing?', name):
    logger.progress(line)
Run Code Online (Sandbox Code Playgroud)

并有一个

def progress(self, txt, *args):
    self.log(self.PROGRESS, txt, *args)
Run Code Online (Sandbox Code Playgroud)

也许它会帮你省去很多麻烦。

编辑2:不,那没有帮助。现在它会显示您progress的调用者函数名称......

  • 是的,Logger 类不够灵活,无法涵盖此用例(它假设需要记录的“funcName”是执行日志记录的函数)。也许可以将“stack_depth”参数添加到记录器初始化中?我认为这是一个足够常见的用例,可以将其添加到库存记录器中。目前,我将按照 Kobi K 的建议将特定处理添加到我的 `split_line` 函数中,并完全跳过 `funcName` 说明符,这对我来说不起作用。遗憾的是我对 funcName 的位置不灵活:我必须将其添加到我的日志消息中。 (2认同)
  • @dangonfast,Python 3.8 中添加了“stacklevel”参数。 (2认同)