【问题标题】:In a trace decorator how to retrieve the line number of the return statement that returned from the decorated function?在跟踪装饰器中,如何检索从装饰函数返回的返回语句的行号?
【发布时间】:2016-09-25 14:56:42
【问题描述】:

在 Python 3.5 中,我正在编写一个元类,它将跟踪装饰器(方法进入和退出的日志记录)添加到所有方法。我正在使用 logging.getLogRecordFactory()/setLogRecordFactory() 在调用日志函数之前修改 LogRecord 中的某些字段,特别是我想修改“lineno”。对于入口跟踪我使用 co_firstlineno,对于由于异常导致的退出跟踪,我使用的是 traceback 的 tb_lineno。

如何检索从修饰函数返回的 return 语句的行号(可能有多个这样的语句)或该函数的最后一条语句的行号,以防它在没有返回语句的情况下返回?

【问题讨论】:

    标签: python-3.x logging return decorator inspection


    【解决方案1】:

    sys.settrace() 解决了我的问题。

    这里是我的元类的完整代码,它将进入/退出跟踪添加到使用类的所有方法:

    import functools
    import inspect
    import logging
    import sys
    import types
    
    TRACE = 5
    logging.addLevelName(TRACE, "TRACE")
    
    
    def _trace(self, msg, *args, **kwargs):
        if self.isEnabledFor(TRACE):
            self._log(TRACE, msg, args, **kwargs)
    
    logging.Logger.trace = _trace
    
    
    def get_logger(func, cls=None):
        logger_name = "%s.%s" % (
            func.__module__,
            func.__qualname__ if cls is None else
            "%s.%s" % (
                cls.__qualname__,
                func.__qualname__))
        return logging.getLogger(logger_name)
    
    
    def trace(func, cls=None):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            old_record_factory = logging.getLogRecordFactory()
    
            code = inspect.unwrap(func).__code__
            pathname = code.co_filename
            funcname = func.__name__
    
            def create_record_factory(lineno):
                def record_factory(*args, **kwargs):
                    record = old_record_factory(*args, **kwargs)
                    record.lineno = lineno
                    record.pathname = pathname
                    record.funcName = funcname
                    return record
                return record_factory
    
            logger = get_logger(func, cls)
    
            exception_line = None
            return_line = None
    
            def call_tracer(frame, event, arg):
                if event == 'call' and frame.f_code == code:
                    return exit_tracer
    
            def exit_tracer(frame, event, arg):
                nonlocal exception_line, return_line
    
                if event == 'return':
                    return_line = frame.f_lineno
                elif event == 'exception':
                    exception_line = frame.f_lineno
                return exit_tracer
    
            def trace_wrapper(msg, lineno):
                logging.setLogRecordFactory(create_record_factory(lineno))
                logger.trace(msg)
                logging.setLogRecordFactory(old_record_factory)
    
            signature = inspect.signature(func)
            bound_args = signature.bind(*args, **kwargs)
    
            trace_wrapper(
                ">>> %s(%s)" % (
                    func.__name__,
                    ", ".join(
                        "%s=%r" % item
                        for item in bound_args.arguments.items())),
                code.co_firstlineno)
    
            old_tracer = sys.gettrace()
            try:
                sys.settrace(call_tracer)
                result = func(*args, **kwargs)
                trace_wrapper(
                    "<<< %s returns %r" % (func.__name__, result),
                    return_line)
                return result
            except Exception as exc:
                trace_wrapper(
                    "<<< %s raises %r in line %d" % (
                        func.__name__, exc, exception_line),
                    return_line)
                raise
            finally:
                sys.settrace(old_tracer)
        return wrapper
    
    
    class LoggingMeta(type):
    
        def __init__(cls, cls_name, bases, cls_dict, **kwargs):
            super().__init__(cls_name, bases, cls_dict, **kwargs)
            for attr_name, attr_value in cls.__dict__.items():
                if isinstance(attr_value, classmethod):
                    setattr(cls, attr_name, classmethod(
                        trace(attr_value.__func__)))
                elif isinstance(attr_value, staticmethod):
                    setattr(cls, attr_name, staticmethod(
                        trace(attr_value.__func__)))
                elif isinstance(attr_value, types.FunctionType):
                    setattr(cls, attr_name, trace(attr_value))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-06-24
      • 1970-01-01
      • 2020-04-28
      • 2016-10-20
      • 1970-01-01
      • 2020-02-10
      • 2019-11-05
      • 2021-01-17
      相关资源
      最近更新 更多