from pandac.PandaModules import getConfigShowbase from direct.directnotify.DirectNotifyGlobal import directNotify from direct.showbase.PythonUtil import fastRepr import sys import types import traceback notify = directNotify.newCategory("ExceptionVarDump") config = getConfigShowbase() reentry = 0 def _varDump__init__(self, *args, **kArgs): global reentry if reentry > 0: return reentry += 1 # frame zero is this frame f = 1 self._savedExcString = None self._savedStackFrames = [] while True: try: frame = sys._getframe(f) except ValueError, e: break else: f += 1 self._savedStackFrames.append(frame) self._moved__init__(*args, **kArgs) reentry -= 1 sReentry = 0 def _varDump__print(exc): global sReentry global notify if sReentry > 0: return sReentry += 1 if not exc._savedExcString: s = '' foundRun = False for frame in reversed(exc._savedStackFrames): filename = frame.f_code.co_filename codename = frame.f_code.co_name if not foundRun and codename != 'run': # don't print stack frames before run(), # they contain builtins and are huge continue foundRun = True s += '\nlocals for %s:%s\n' % (filename, codename) locals = frame.f_locals for var in locals: obj = locals[var] rep = fastRepr(obj) s += '::%s = %s\n' % (var, rep) exc._savedExcString = s exc._savedStackFrames = None notify.info(exc._savedExcString) sReentry -= 1 oldExcepthook = None # store these values here so that Task.py can always reliably access them # from its main exception handler wantStackDumpLog = False wantStackDumpUpload = False variableDumpReasons = [] dumpOnExceptionInit = False class _AttrNotFound: pass def _excepthookDumpVars(eType, eValue, tb): origTb = tb excStrs = traceback.format_exception(eType, eValue, origTb) s = 'printing traceback in case variable repr crashes the process...\n' for excStr in excStrs: s += excStr notify.info(s) s = 'DUMPING STACK FRAME VARIABLES' #import pdb;pdb.set_trace() #foundRun = False foundRun = True while tb is not None: frame = tb.tb_frame code = frame.f_code # this is a list of every string identifier used in this stack frame's code codeNames = set(code.co_names) # skip everything before the 'run' method, those frames have lots of # not-useful information if not foundRun: if code.co_name == 'run': foundRun = True else: tb = tb.tb_next continue s += '\n File "%s", line %s, in %s' % ( code.co_filename, frame.f_lineno, code.co_name) stateStack = Stack() # prime the stack with the variables we should visit from the frame's data structures # grab all of the local, builtin and global variables that appear in the code's name list name2obj = {} for name, obj in frame.f_builtins.items(): if name in codeNames: name2obj[name] = obj for name, obj in frame.f_globals.items(): if name in codeNames: name2obj[name] = obj for name, obj in frame.f_locals.items(): if name in codeNames: name2obj[name] = obj # show them in alphabetical order names = name2obj.keys() names.sort() # push them in reverse order so they'll be popped in the correct order names.reverse() traversedIds = set() for name in names: stateStack.push([name, name2obj[name], traversedIds]) while len(stateStack) > 0: name, obj, traversedIds = stateStack.pop() #notify.info('%s, %s, %s' % (name, fastRepr(obj), traversedIds)) r = fastRepr(obj, maxLen=10) if type(r) is types.StringType: r = r.replace('\n', '\\n') s += '\n %s = %s' % (name, r) # if we've already traversed through this object, don't traverse through it again if id(obj) not in traversedIds: attrName2obj = {} for attrName in codeNames: attr = getattr(obj, attrName, _AttrNotFound) if (attr is not _AttrNotFound): # prevent infinite recursion on method wrappers (__init__.__init__.__init__...) try: className = attr.__class__.__name__ except: pass else: if className == 'method-wrapper': continue attrName2obj[attrName] = attr if len(attrName2obj): # show them in alphabetical order attrNames = attrName2obj.keys() attrNames.sort() # push them in reverse order so they'll be popped in the correct order attrNames.reverse() ids = set(traversedIds) ids.add(id(obj)) for attrName in attrNames: obj = attrName2obj[attrName] stateStack.push(['%s.%s' % (name, attrName), obj, ids]) tb = tb.tb_next if foundRun: s += '\n' if wantStackDumpLog: notify.info(s) if wantStackDumpUpload: excStrs = traceback.format_exception(eType, eValue, origTb) for excStr in excStrs: s += excStr timeMgr = None try: timeMgr = base.cr.timeManager except: try: timeMgr = simbase.air.timeManager except: pass if timeMgr: timeMgr.setStackDump(s) oldExcepthook(eType, eValue, origTb) def install(log, upload): global oldExcepthook global wantStackDumpLog global wantStackDumpUpload global dumpOnExceptionInit wantStackDumpLog = log wantStackDumpUpload = upload dumpOnExceptionInit = config.GetBool('variable-dump-on-exception-init', 0) if dumpOnExceptionInit: # this mode doesn't completely work because exception objects # thrown by the interpreter don't get created until the # stack has been unwound and an except block has been reached if not hasattr(Exception, '_moved__init__'): Exception._moved__init__ = Exception.__init__ Exception.__init__ = _varDump__init__ else: if sys.excepthook is not _excepthookDumpVars: oldExcepthook = sys.excepthook sys.excepthook = _excepthookDumpVars