自定义记录器类和日志中的正确行号/功能名称

Ala*_*sta 9 python logging

我想将Python记录器包装在一个自定义类中,以嵌入一些特定于应用程序的功能,并隐藏开发人员的设置细节(设置文件输出,日志记录级别等).为此,我使用以下API创建了一个类:

__init__(log_level, filename)
debug(msg)
info(msg)
warning(msg)
error(msg)
Run Code Online (Sandbox Code Playgroud)

Logger.debug/info/warning/etc调用通常在日志中写入进行日志调用的函数和行号.但是,使用我的自定义类,写入日志文件的函数和行号始终相同(对应于自定义类中的debug()/ info()/ warning()/ error()函数).我希望它保存记录msg的应用程序代码行.那可能吗?

提前致谢.

Art*_*hur 9

Try ,它计算从原始日志记录调用到记录器的、等调用的stacklevel调用次数。日志记录3.8中的新增内容:debug()info()

第三个可选关键字参数是 stacklevel,默认为 1。如果大于 1,则在计算为日志记录事件创建的 LogRecord 中设置的行号和函数名称时,将跳过相应数量的堆栈帧。这可以用于记录帮助程序,以便记录的函数名称、文件名和行号不是帮助程序函数/方法的信息,而是其调用者的信息。该参数的名称反映了警告模块中的等效参数。

  • 这是正确的答案@Alan Evangelista,它不需要修补,不需要复制代码,没有什么可能会在以后中断,只需在调用底层记录器信息、跟踪、调试(等)调用时传递堆栈级别 kwarg,它就会正常工作。 (3认同)

Nei*_*ais 5

如果您愿意重新实现一点标准日志记录模块,则可以生成日志包装器。诀窍是编写自己的 findCaller() 方法,该方法知道在解释回溯时如何忽略日志包装器源文件。

在 logwrapper.py 中:

import logging
import os
import sys

from logging import *


# This code is mainly copied from the python logging module, with minor modifications

# _srcfile is used when walking the stack to check when we've got the first
# caller stack frame.
#
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 LogWrapper(object):
    def __init__(self, logger):
        self.logger = logger

    def debug(self, msg, *args, **kwargs):
        """
        Log 'msg % args' with severity 'DEBUG'.

        To pass exception information, use the keyword argument exc_info with
        a true value, e.g.

        logger.debug("Houston, we have a %s", "thorny problem", exc_info=1)
        """
        if self.logger.isEnabledFor(DEBUG):
            self._log(DEBUG, msg, args, **kwargs)

    def info(self, msg, *args, **kwargs):
        """
        Log 'msg % args' with severity 'INFO'.

        To pass exception information, use the keyword argument exc_info with
        a true value, e.g.

        logger.info("Houston, we have a %s", "interesting problem", exc_info=1)
        """
        if self.logger.isEnabledFor(INFO):
            self._log(INFO, msg, args, **kwargs)


    # Add other convenience methods

    def log(self, level, msg, *args, **kwargs):
        """
        Log 'msg % args' with the integer severity 'level'.

        To pass exception information, use the keyword argument exc_info with
        a true value, e.g.

        logger.log(level, "We have a %s", "mysterious problem", exc_info=1)
        """
        if not isinstance(level, int):
            if logging.raiseExceptions:
                raise TypeError("level must be an integer")
            else:
                return
        if self.logger.isEnabledFor(level):
            self._log(level, msg, args, **kwargs)


    def _log(self, level, msg, args, exc_info=None, extra=None):
        """
        Low-level logging routine which creates a LogRecord and then calls
        all the handlers of this logger to handle the record.
        """
        # Add wrapping functionality here.
        if _srcfile:
            #IronPython doesn't track Python frames, so findCaller throws an
            #exception on some versions of IronPython. We trap it here so that
            #IronPython can use logging.
            try:
                fn, lno, func = self.findCaller()
            except ValueError:
                fn, lno, func = "(unknown file)", 0, "(unknown function)"
        else:
            fn, lno, func = "(unknown file)", 0, "(unknown function)"
        if exc_info:
            if not isinstance(exc_info, tuple):
                exc_info = sys.exc_info()
        record = self.logger.makeRecord(
            self.logger.name, level, fn, lno, msg, args, exc_info, func, extra)
        self.logger.handle(record)


    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()
        #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)

以及一个使用它的例子:

import logging
from logwrapper import LogWrapper

logging.basicConfig(level=logging.DEBUG, format="%(asctime)s %(filename)s(%(lineno)d): "
                    "%(message)s")
logger = logging.getLogger()
lw = LogWrapper(logger)

lw.info('Wrapped well, this is interesting')
Run Code Online (Sandbox Code Playgroud)