Showing the right funcName when wrapping logger functionality in a custom class

前端 未结 8 1678
你的背包
你的背包 2021-02-01 16:31

This is the formatting string that I am using for logging:

\'%(asctime)s - %(levelname)-10s - %(funcName)s - %(message)s\'

But to show the logg

相关标签:
8条回答
  • 2021-02-01 16:56

    Thanks to @cygnusb and the others who already provided useful pointers. I chose to use the Python 3.4 Logger.findCaller method as my starting point. The following solution has been tested with Python 2.7.9 and 3.4.2. This code is meant to be placed in its own module. It produces the correct answer with only one iteration of the loop.

    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
    
    0 讨论(0)
  • 2021-02-01 16:56

    As suggested in the first answer, subclassing the Logger class and using logging.setLoggerClass should do the trick. You will need a modified findCaller function, that is handling your wrapped function call(s).

    Put the following into a module, since the findCaller class method is searching the first call from a file, which is not the current source filename.

    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")
    
    0 讨论(0)
提交回复
热议问题