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