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 fnmatch
import socket import socket
import traceback import traceback
import threading
import types import types
import sys
from collections import defaultdict from collections import defaultdict
from oyoyo.client import IRCClient from oyoyo.client import IRCClient
@ -20,6 +22,14 @@ HOOKS = defaultdict(list)
# Error handler decorators # Error handler decorators
_do_nothing = lambda: None
class _local(threading.local):
level = 0
frame_locals = None
_local = _local()
class handle_error: class handle_error:
def __new__(cls, func): def __new__(cls, func):
@ -36,25 +46,43 @@ class handle_error:
return self return self
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
fn = _do_nothing
_local.level += 1
try: try:
return self.func(*args, **kwargs) return self.func(*args, **kwargs)
except Exception: except Exception:
traceback.print_exc() # no matter what, we want it to print if _local.frame_locals is None:
if kwargs.get("cli"): # client exc_type, exc_value, tb = sys.exc_info()
cli = kwargs["cli"] while tb.tb_next is not None:
else: tb = tb.tb_next
for cli in args: _local.frame_locals = tb.tb_frame.f_locals
if isinstance(cli, IRCClient):
break if _local.level > 1:
else: raise # the outermost caller should handle this
fn = lambda: errlog("\n" + data)
data = traceback.format_exc()
cli = None 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 cli is not None:
if not botconfig.PASTEBIN_ERRORS or botconfig.CHANNEL != botconfig.DEV_CHANNEL: if not botconfig.PASTEBIN_ERRORS or botconfig.CHANNEL != botconfig.DEV_CHANNEL:
cli.msg(botconfig.CHANNEL, messages["error_log"]) cli.msg(botconfig.CHANNEL, messages["error_log"])
errlog(traceback.format_exc())
if botconfig.PASTEBIN_ERRORS and botconfig.DEV_CHANNEL: 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: class cmd:
def __init__(self, *cmds, raw_nick=False, flag=None, owner_only=False, def __init__(self, *cmds, raw_nick=False, flag=None, owner_only=False,