Show local variables from all possibly relevant frames

This doesn't show the variables for a few specific functions, specifically those which have a '_ignore_locals_' local variable set to a truthy value. For now, those functions are handle_error.__call__ (which is present in every traceback ever, due to its nature), cmd.caller and command.caller (one or the other is present in a large number of tracebacks, again due to their nature).

This makes debugging easier when the error is not in the innermost frame, but rather in one or two frames before, and the locals are lost.
This commit is contained in:
Vgr E. Barry 2017-01-25 14:05:51 -05:00
parent a928d729a6
commit b68cba12d6

View File

@ -27,7 +27,6 @@ HOOKS = defaultdict(list)
# Error handler decorators and context managers
class _local(threading.local):
frame_locals = None
handler = None
level = 0
@ -77,28 +76,37 @@ class print_traceback:
_local.level -= 1
return False
if _local.frame_locals is None:
while tb.tb_next is not None:
tb = tb.tb_next
_local.frame_locals = tb.tb_frame.f_locals
if _local.level > 1:
_local.level -= 1
return False # the outermost caller should handle this
frames = []
while tb is not None:
if tb.tb_frame.f_locals.get("_ignore_locals_") or not tb.tb_frame.f_locals:
frames.append(None)
else:
frames.append(tb.tb_frame)
tb = tb.tb_next
if _local.handler is None:
_local.handler = chain_exceptions(exc_value)
variables = ["", None, "Local variables from innermost frame:", ""]
variables = ["", None, None]
with _local.handler:
for name, value in _local.frame_locals.items():
variables.append("{0} = {1!r}".format(name, value))
for i, frame in enumerate(frames, start=1):
if frame is None:
continue
variables.append("\nLocal variables from frame #{0} (in {1}):\n".format(i, frame.f_code.co_name))
for name, value in frame.f_locals.items():
variables.append("{0} = {1!r}".format(name, value))
if len(variables) > 4:
if len(variables) > 3:
variables.append("\n")
variables[2] = "Local variables in all frames (most recent call last):"
else:
variables[2] = "No local variables found in innermost frame."
variables[2] = "No local variables found in all frames."
variables[1] = _local.handler.traceback
@ -176,6 +184,7 @@ class handle_error:
return self
def __call__(*args, **kwargs):
_ignore_locals_ = True
self, *args = args
if self.instance is not None:
args = [self.instance] + args
@ -226,6 +235,7 @@ class command:
@handle_error
def caller(self, cli, rawnick, chan, rest):
_ignore_locals_ = True
user = users._add(cli, nick=rawnick) # FIXME
if users.equals(chan, users.Bot.nick): # PM
@ -339,6 +349,7 @@ class cmd:
@handle_error
def caller(self, cli, rawnick, chan, rest):
_ignore_locals_ = True
if users.equals(chan, users.Bot.nick):
chan = users.parse_rawnick_as_dict(rawnick)["nick"]