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).
This commit is contained in:
Vgr E. Barry 2016-10-30 22:34:46 -04:00
parent a2257ce692
commit d602a33efd

View File

@ -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,