From d602a33efd66c26972cc59efc6f1e63bdf936c98 Mon Sep 17 00:00:00 2001 From: "Vgr E. Barry" Date: Sun, 30 Oct 2016 22:34:46 -0400 Subject: [PATCH] Update handle_error to be more useful Now, local variables from the innermost frame (i.e. where the error was raised) are printed with the traceback, and the latter includes all of the call stack (instead of just up to the innermost error handler). --- src/decorators.py | 50 ++++++++++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/src/decorators.py b/src/decorators.py index 639010f..d0e5497 100644 --- a/src/decorators.py +++ b/src/decorators.py @@ -1,7 +1,9 @@ import fnmatch import socket import traceback +import threading import types +import sys from collections import defaultdict from oyoyo.client import IRCClient @@ -20,6 +22,14 @@ HOOKS = defaultdict(list) # Error handler decorators +_do_nothing = lambda: None + +class _local(threading.local): + level = 0 + frame_locals = None + +_local = _local() + class handle_error: def __new__(cls, func): @@ -36,25 +46,43 @@ class handle_error: return self def __call__(self, *args, **kwargs): + fn = _do_nothing + _local.level += 1 try: return self.func(*args, **kwargs) except Exception: - traceback.print_exc() # no matter what, we want it to print - if kwargs.get("cli"): # client - cli = kwargs["cli"] - else: - for cli in args: - if isinstance(cli, IRCClient): - break - else: - cli = None + if _local.frame_locals is None: + exc_type, exc_value, tb = sys.exc_info() + while tb.tb_next is not None: + tb = tb.tb_next + _local.frame_locals = tb.tb_frame.f_locals + + if _local.level > 1: + raise # the outermost caller should handle this + + fn = lambda: errlog("\n" + data) + data = traceback.format_exc() + cli = None + variables = ["\nLocal variables from innermost frame:\n"] + for name, value in _local.frame_locals.items(): + if isinstance(value, IRCClient): + cli = value + + variables.append("{0} = {1!r}".format(name, value)) + + data += "\n".join(variables) if cli is not None: if not botconfig.PASTEBIN_ERRORS or botconfig.CHANNEL != botconfig.DEV_CHANNEL: cli.msg(botconfig.CHANNEL, messages["error_log"]) - errlog(traceback.format_exc()) if botconfig.PASTEBIN_ERRORS and botconfig.DEV_CHANNEL: - pastebin_tb(cli, messages["error_log"], traceback.format_exc()) + pastebin_tb(cli, messages["error_log"], data) + + finally: + fn() + _local.level -= 1 + if not _local.level: # outermost caller; we're done here + _local.frame_locals = None class cmd: def __init__(self, *cmds, raw_nick=False, flag=None, owner_only=False,