Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/logging/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Python 在自定义类中包装记录器功能时显示正确的funcName_Python_Logging - Fatal编程技术网

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

Python 在自定义类中包装记录器功能时显示正确的funcName,python,logging,Python,Logging,这是我用于日志记录的格式化字符串: '%(asctime)s - %(levelname)-10s - %(funcName)s - %(message)s' 但是为了显示日志消息,我有一个包装器做了更多的工作(我设置了不同的日志级别,配置了不同的日志后端,提供了访问自定义级别的方便函数,等等): 使用此设置,每当我记录某些内容时: def myfunc(): log.progress('Hello') 我得到: 013-10-27 08:47:30,130 - PROGRESS

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

'%(asctime)s - %(levelname)-10s - %(funcName)s - %(message)s'
但是为了显示日志消息,我有一个包装器做了更多的工作(我设置了不同的日志级别,配置了不同的日志后端,提供了访问自定义级别的方便函数,等等):

使用此设置,每当我记录某些内容时:

def myfunc():
    log.progress('Hello')
我得到:

013-10-27 08:47:30,130 - PROGRESS   - split_line - Hello
这不是我想要的,也就是说:

013-10-27 08:47:30,130 - PROGRESS   - myfunc     - Hello
我如何告诉记录器为函数名使用正确的上下文?我认为这实际上会比stackframe高出两层

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

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)
产生:

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?

首先,根据您的代码,很清楚发生这种情况的原因,
levelname
funcName
属于
self.log
,因此当您调用
self.log(level,line)
时,
levelname
level
funcName
line

您有两个选项IMHO:

  • 要使用
    inspect
    模块获取当前方法并在消息中传递它,您可以解析它并非常轻松地使用它

  • 更好的方法是在分割线内使用
    inspect
    来获得“父”方法 您可以将下面代码中的数字(3)更改为“播放”方法的层次结构

  • 使用inspect获取当前方法的示例

    from inspect import stack
    
    class Foo:
        def __init__(self):
            print stack()[0][3]
    
    f = Foo()
    

    本质上,应该归咎于
    记录器
    类:

    这种方法

    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
    
    返回调用程序链中不属于当前模块的第一个函数

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


    在您非常特殊的情况下,避免自动分线可能会更简单

    logger.progress('Hello %s', name)
    logger.progress('How are you doing?')
    
    或者

    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)
    
    祝你生日快乐

    def progress(self, txt, *args):
        self.log(self.PROGRESS, txt, *args)
    
    也许这会帮你省去很多头痛


    编辑2:不,那没用。现在,它将显示
    progress
    作为调用方函数名…

    您可以合并
    progress
    方法和
    split\u line
    方法:

    def progress(self, txt, *args, **kwargs):
        if self.isEnabledFor(self.PROGRESS):
            txt = txt % (args)
            for line in txt.split('\n'):
                self._log(self.PROGRESS, line, [], **kwargs)
    

    正如第一个答案中所建议的,对Logger类进行子类化并使用logging.setLoggerClass应该可以做到这一点。您将需要一个修改过的findCaller函数,该函数用于处理包装函数调用

    将以下内容放入模块中,因为findCaller类方法正在搜索来自文件(不是当前源文件名)的第一个调用

    import inspect
    import logging
    import os
    
    if hasattr(sys, 'frozen'): #support for py2exe
        _srcfile = "logging%s__init__%s" % (os.sep, __file__[-4:])
    elif __file__[-4:].lower() in ['.pyc', '.pyo']:
        _srcfile = __file__[:-4] + '.py'
    else:
        _srcfile = __file__
    _srcfile = os.path.normcase(_srcfile)
    
    class WrappedLogger(logging.Logger):
        def __init__(self,name):
            logging.Logger.__init__(self, name)
    
        def findCaller(self):
            """
            Find the stack frame of the caller so that we can note the source
            file name, line number and function name.
            """
            # get all outer frames starting from the current frame
            outer = inspect.getouterframes(inspect.currentframe())
            # reverse the order, to search from out inward
            outer.reverse()
            rv = "(unknown file)", 0, "(unknown function)"    
    
            pos = 0
            # go through all frames
            for i in range(0,len(outer)):
                # stop if we find the current source filename
                if outer[i][1] == _srcfile:
                    # the caller is the previous one
                    pos=i-1
                    break
    
            # get the frame (stored in first tuple entry)
            f = outer[pos][0]
    
            co = f.f_code
            rv = (co.co_filename, f.f_lineno, co.co_name)
    
            return rv
    # Usage:
    logging.setLoggerClass(WrappedLogger)
    log = logging.getLogger("something")
    

    感谢@cygnusb和其他已经提供了有用指针的人。我选择使用Python 3.4 Logger.findCaller方法作为起点。以下解决方案已经用Python 2.7.9和3.4.2进行了测试。这段代码应该放在它自己的模块中。它只需循环一次即可生成正确答案

    import io
    import sys
    
    def _DummyFn(*args, **kwargs):
        """Placeholder function.
    
        Raises:
            NotImplementedError
        """
        _, _ = args, kwargs
        raise NotImplementedError()
    
    # _srcfile is used when walking the stack to check when we've got the first
    # caller stack frame, by skipping frames whose filename is that of this
    # module's source. It therefore should contain the filename of this module's
    # source file.
    _srcfile = os.path.normcase(_DummyFn.__code__.co_filename)
    if hasattr(sys, '_getframe'):
        def currentframe():
            return sys._getframe(3)
    else:  # pragma: no cover
        def currentframe():
            """Return the frame object for the caller's stack frame."""
            try:
                raise Exception
            except Exception:
                return sys.exc_info()[2].tb_frame.f_back
    
    class WrappedLogger(logging.Logger):
        """Report context of the caller of the function that issues a logging call.
    
        That is, if
    
            A() -> B() -> logging.info()
    
        Then references to "%(funcName)s", for example, will use A's context
        rather than B's context.
    
        Usage:
            logging.setLoggerClass(WrappedLogger)
            wrapped_logging = logging.getLogger("wrapped_logging")
        """
        def findCaller(self, stack_info=False):
            """Return the context of the caller's parent.
    
            Find the stack frame of the caller so that we can note the source
            file name, line number and function name.
    
            This is based on the standard python 3.4 Logger.findCaller method.
            """
            sinfo = None
            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
    
            if sys.version_info.major == 2:
                rv = "(unknown file)", 0, "(unknown function)"
            else:
                rv = "(unknown file)", 0, "(unknown function)", sinfo
    
            while hasattr(f, "f_code"):
                co = f.f_code
                filename = os.path.normcase(co.co_filename)
                if filename == _srcfile or filename == logging._srcfile:
                    f = f.f_back
                    continue
                # We want the frame of the caller of the wrapped logging function.
                # So jump back one more frame.
                f = f.f_back
                co = f.f_code
                if sys.version_info.major == 2:
                rv = "(unknown file)", 0, "(unknown function)"
            else:
                rv = "(unknown file)", 0, "(unknown function)", sinfo
    
            while hasattr(f, "f_code"):
                co = f.f_code
                filename = os.path.normcase(co.co_filename)
                if filename == _srcfile or filename == logging._srcfile:
                    f = f.f_back
                    continue
                # We want the frame of the caller of the wrapped logging function.
                # So jump back one more frame.
                f = f.f_back
                co = f.f_code
                if sys.version_info.major == 2:
                    rv = co.co_filename, f.f_lineno, co.co_name
                else:
                    if stack_info:
                        sio = io.StringIO()
                        sio.write('Stack (most recent call last):\n')
                        traceback.print_stack(f, file=sio)
                        sinfo = sio.getvalue()
                        if sinfo[-1] == '\n':
                            sinfo = sinfo[:-1]
                        sio.close()
                    rv = co.co_filename, f.f_lineno, co.co_name, sinfo
                break
    
            return rv
    

    多亏了@glglgl,我才有了高级搜索引擎

    请注意
    \u logging\u srcfile
    \u此文件
    的初始化-灵感来自

    当然,您可以将自己的规则放在
    findCaller()
    -这里我只是排除了自定义记录器所在文件中的所有内容,除了
    test\u logging
    函数

    重要信息只有将名称传递给
    getLogger(name)
    工厂时,才会检索自定义记录器。如果您只需执行
    logging.getLogger()
    ,您将得到一个不是您的记录器的RootLogger

    导入系统 导入操作系统 导入日志记录 #从检查导入currentframe currentframe=lambda:sys.\u getframe(3) _logging\u srcfile=os.path.normcase(logging.addLevelName.\uuuu代码\uuuu.co\u文件名) _此\u srcfile=\u文件__ def test_logging(): logger=logging.getLogger('test') handler=logging.StreamHandler(sys.stderr) handler.setFormatter(logging.Formatter('%(funcName)s:%(message)s')) handler.setLevel(11) logger.addHandler(处理程序) logger.debug('将不打印') logger.your_函数(“测试我”) 类CustomLogger(logging.getLoggerClass()): def u u init u;(self,name,level=logging.NOTSET): 超级(自定义记录器,自我)。\uuuuu初始化\uuuuuuu(名称,级别) 定义您的_函数(self、msg、*args、**kwargs): #不管你想在这里做什么。。。 自我记录(12,消息,参数,**kwargs) def findCaller(自): """ 找到调用者的堆栈帧,以便我们可以记录源代码 文件名、行号和函数名。 此函数直接来自原始python函数 """ f=当前帧() #在某些版本的IronPython上,如果 #IronPython不能与-X:Frames一起运行。 如果f不是无: f=f.f_后 rv=“(未知文件)”,0“(未知函数)” 而hasattr(f,“f_代码”): co=f.f_代码 filename=os.path.normcase(co.co_文件名) ##原始状态 #如果文件名==\u日志记录\u文件: ##在这里输入您的自定义条件,例如: ##也跳过此文件,但用于调试的test_日志记录方法除外 如果公司名称!='在[\u logging\u srcfile,\u this\u srcfile]中测试\u logging'和文件名: f=f.f_后 持续 rv=(co.co_文件名,f.f_行号,co.co_名称) 打破 返回rv logging.setLoggerClass(CustomLogger)
    有人给出了正确的答案。我将做一个总结

    ,它通过原始
    日志记录
    包过滤堆栈帧

    所以我们做同样的事情,过滤我们自己的记录器包装
    my\u log\u模块。\u srcfile
    。我们将动态替换记录器实例的方法

    顺便说一句,请不要创建
    logging.Logger
    的子类,
    logging
    包没有OOP设计,当findCaller,pitty…是吗

    # file: my_log_module.py, Python-2.7, define your logging wrapper here
    import sys
    import os
    import logging
    my_logger = logging.getLogger('my_log')
    
    if hasattr(sys, '_getframe'): currentframe = lambda: sys._getframe(3)
    # done filching
    
    #
    # _srcfile is used when walking the stack to check when we've got the first
    # caller stack frame.
    #
    _srcfile = os.path.normcase(currentframe.__code__.co_filename)
    
    def findCallerPatch(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
    
    # DO patch
    my_logger.findCaller = findCallerPatch
    
    # file: my_log_module.py, Python-2.7, define your logging wrapper here
    import sys
    import os
    import logging
    my_logger = logging.getLogger('my_log')
    
    if hasattr(sys, '_getframe'): currentframe = lambda: sys._getframe(3)
    # done filching
    
    #
    # _srcfile is used when walking the stack to check when we've got the first
    # caller stack frame.
    #
    _srcfile = os.path.normcase(currentframe.__code__.co_filename)
    
    def findCallerPatch(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
    
    # DO patch
    my_logger.findCaller = findCallerPatch
    
    # file: app.py
    from my_log_module import my_logger
    my_logger.debug('I can check right caller now')
    
    # file: my_log_modue.py
    import logging
    my_logger = logging.getLogger('my_log')
    
    class MyLogger(logging.Logger):
        ...
    
    my_logger.__class__ = MyLogger
    
    import sys,os
    
    #Get both logger's and this file's path so the wrapped logger can tell when its looking at the code stack outside of this file.
    _loggingfile = os.path.normcase(logging.__file__)
    if hasattr(sys, 'frozen'): #support for py2exe
        _srcfile = "logging%s__init__%s" % (os.sep, __file__[-4:])
    elif __file__[-4:].lower() in ['.pyc', '.pyo']:
        _srcfile = __file__[:-4] + '.py'
    else:
        _srcfile = __file__
    _srcfile = os.path.normcase(_srcfile)
    _wrongCallerFiles = set([_loggingfile, _srcfile])
    
    #Subclass the original logger and overwrite findCaller
    class WrappedLogger(logging.Logger):
        def __init__(self, name):
            logging.Logger.__init__(self, name)
    
        #Modified slightly from cpython's implementation https://github.com/python/cpython/blob/master/Lib/logging/__init__.py#L1374
        def findCaller(self, stack_info=False, stacklevel=1):
            """
            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
            orig_f = f
            while f and stacklevel > 1:
                f = f.f_back
                stacklevel -= 1
            if not f:
                f = orig_f
            rv = "(unknown file)", 0, "(unknown function)", None
            while hasattr(f, "f_code"):
                co = f.f_code
                filename = os.path.normcase(co.co_filename)
                if filename in _wrongCallerFiles:
                    f = f.f_back
                    continue
                sinfo = None
                if stack_info:
                    sio = io.StringIO()
                    sio.write('Stack (most recent call last):\n')
                    traceback.print_stack(f, file=sio)
                    sinfo = sio.getvalue()
                    if sinfo[-1] == '\n':
                    sinfo = sinfo[:-1]
                sio.close()
            rv = (co.co_filename, f.f_lineno, co.co_name, sinfo)
            break
        return rv
    
    def __init__(self):
        ...
        self._logger = logging.getLogger(__name__)
    
    def debug(self, msg, *args, **kwargs):
        self._logger.debug(msg, *args, stacklevel=2, **kwargs)