| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200 |
- __all__ = ["install"]
- from panda3d.core import ConfigVariableBool
- from direct.directnotify.DirectNotifyGlobal import directNotify
- from direct.showbase.PythonUtil import fastRepr, Stack
- import sys
- import traceback
- notify = directNotify.newCategory("ExceptionVarDump")
- 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 as e:
- break
- else:
- f += 1
- self._savedStackFrames.append(frame)
- self._moved__init__(*args, **kArgs)
- reentry -= 1
- sReentry = 0
- def _varDump__print(exc) -> None:
- 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: list = []
- 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
- traversedIds = set()
- # push them in reverse alphabetical order so they'll be popped in the correct order
- for name in sorted(name2obj, reverse=True):
- 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 isinstance(r, str):
- 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 Exception:
- pass
- else:
- if className == 'method-wrapper':
- continue
- attrName2obj[attrName] = attr
- if len(attrName2obj) > 0:
- ids = set(traversedIds)
- ids.add(id(obj))
- # push them in reverse alphabetical order so they'll be popped in the correct order
- for attrName in sorted(attrName2obj, reverse=True):
- 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 Exception:
- try:
- timeMgr = simbase.air.timeManager
- except Exception:
- pass
- if timeMgr:
- timeMgr.setStackDump(s)
- oldExcepthook(eType, eValue, origTb)
- def install(log: bool, upload: bool) -> None:
- """Installs the exception hook."""
- global oldExcepthook
- global wantStackDumpLog
- global wantStackDumpUpload
- global dumpOnExceptionInit
- wantStackDumpLog = log
- wantStackDumpUpload = upload
- dumpOnExceptionInit = ConfigVariableBool('variable-dump-on-exception-init', False)
- 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__ # type: ignore[attr-defined]
- Exception.__init__ = _varDump__init__ # type: ignore[method-assign]
- else:
- if sys.excepthook is not _excepthookDumpVars:
- oldExcepthook = sys.excepthook
- sys.excepthook = _excepthookDumpVars
|