import sys import os import inspect import traceback class CallInfo(object): def __init__(self, **kwargs): self.__dict__.update(kwargs) class CallableInfo(object): def __init__(self): self.calldata = [] def add_call(self, frame): code = frame.f_code args, varargs, varkw, locals = inspect.getargvalues(frame) stack = traceback.extract_stack(frame) self.calldata.append(CallInfo(**{ 'stack': stack, 'lineno': frame.f_back.f_lineno, 'filename': frame.f_back.f_code.co_filename, 'exc_type': frame.f_exc_type, 'exc_value': frame.f_exc_value, 'argcount': code.co_argcount, 'args': tuple([(arg, locals[arg]) for arg in args]), 'varargs': varargs and (varargs, locals[varargs]), 'varkw': varkw and (varkw, locals[varkw]), 'locals': locals, 'retval': None, })) def add_return(self, frame, ret): if not self.calldata: return self.calldata[-1].retval = ret def add_exception(self, frame, exc_info): if not self.calldata: return cd = self.calldata[-1] cd.exc_type = exc_info[0] cd.exc_value = exc_info[1] class Tracer(object): def __init__(self, files): self.files = [] for file in files: if file.split('.')[-1] in ('pyc', 'pyo', 'pyw'): file = file[:-1] self.files.append(file) self.callable_infos = {} _oldtrace = None def start(self): self._oldtrace = sys.gettrace() sys.settrace(self._tracefunc) def stop(self): sys.settrace(self._oldtrace) self._oldtrace = None def results(self): return self.callable_infos def get(self, key): return self.callable_infos.get(key) def _tracefunc(self, frame, event, arg): code = frame.f_code if code.co_filename not in self.files: return ci = self._get_callable_info(frame) if event == 'call': ci.add_call(frame) if event == 'return': ci.add_return(frame, arg) elif event == 'exception': ci.add_exception(frame, arg) return self._tracefunc def _get_callable_info(self, frame): key = (frame.f_code.co_filename, frame.f_code.co_firstlineno) if key not in self.callable_infos: ci = self.callable_infos[key] = CallableInfo() else: ci = self.callable_infos[key] return ci if __name__ == '__main__': t = Tracer([__file__]) t.start() class Foo: def foo(self, x): return x + 1 f = Foo() f.foo(1) def bar(x): return x + 1 class SomeIntThingie(object): def __add__(self, x): return x + 1 bar(SomeIntThingie()) def foo(*args, **kwargs): return (args, kwargs) foo(1, 2) foo(bar=1, baz=2) def bar(x): return x + 1 bar(1) try: bar('baz') except: pass t.stop() print '=' * 79 for (filename, lineno), ci in t.callable_infos.iteritems(): flines = open(filename).readlines() line = flines[lineno - 1] if not line.strip().startswith('def '): continue print '%02d %s' % (lineno, line[:-1]) for info in ci.calldata: print print 'arg values:', info['args'] print 'vararg values:', info['varargs'] print 'varkw values:', info['varkw'] if info['exc_type'] is not None: print 'exception raised:', info['exc_type'] print 'exception value:', info['exc_value'] print 'returned:', info.get('retval') for filename, lineno, modname, code in reversed(info['stack']): print '%03d %s' % (lineno, flines[lineno - 1][:-1]) print '=' * 79