2016-05-27 69 views
0

在Python 3.5中,我正在編寫一個元類,它爲所有方法添加了跟蹤裝飾器(方法入口和出口的日誌記錄)。我使用logging.getLogRecordFactory()/ setLogRecordFactory()在調用日誌函數之前修改LogRecord中的某些字段,特別是我想修改「lineno」。對於我使用co_firstlineno的入口跟蹤,由於異常引起的出口跟蹤,我使用了跟蹤的tb_lineno。在跟蹤裝飾器中如何檢索裝飾函數返回的返回語句的行號?

如何從裝飾函數返回的return語句的行號(可能有多個這樣的語句)或該函數的最後一個語句的行號,以防它返回時沒有返回語句?

回答

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)) 
相關問題