0

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

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

4

1 回答 1

0

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))
于 2016-05-27T13:18:23.850 回答