From b28d4bf6e3e6f270f9c0ce392563e49dd6aa02d3 Mon Sep 17 00:00:00 2001 From: "Vgr E. Barry" Date: Wed, 4 Nov 2015 12:41:47 -0500 Subject: [PATCH] Properly fix the error handler --- src/__init__.py | 79 ----------------------------------------------- src/decorators.py | 51 ++++++++++++++++++++++++++++++ src/handler.py | 26 ++-------------- src/wolfgame.py | 11 +++++-- 4 files changed, 63 insertions(+), 104 deletions(-) diff --git a/src/__init__.py b/src/__init__.py index 6e68ea1..feb32bf 100644 --- a/src/__init__.py +++ b/src/__init__.py @@ -1,10 +1,6 @@ -import traceback import argparse import datetime -import socket import time -import sys -import io import botconfig import src.settings as var @@ -117,78 +113,3 @@ def stream(output, level="normal"): stream_handler(output) elif level == "warning": stream_handler(output) - -# Error handler - -buffer = io.BufferedWriter(io.FileIO(file=sys.stderr.fileno(), mode="wb", closefd=False)) - -class ErrorHandler(io.TextIOWrapper): - """Handle tracebacks sent to sys.stderr.""" - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.cli = None - self.target_logger = None - self.data = None - - def write(self, data): - if self.closed: - raise ValueError("write to closed file") - if not isinstance(data, str): - raise ValueError("can't write %s to text stream" % data.__class__.__name__) - length = len(data) - b = data.encode("utf-8", "replace") - self.buffer.write(b) - self.data = data - self.flush() - return length - - def flush(self): - # Probably a syntax error on startup, so these aren't defined yet - # If we do nothing, the error magically is printed to the console - if self.cli is None or self.target_logger is None: - return - - self.buffer.flush() - - if self.data is None: - return - - exc = self.data.rstrip().splitlines()[-1].partition(":")[0] - - import builtins - - if "." in exc: - import importlib - module, dot, name = exc.rpartition(".") - try: - module = importlib.import_module(module) - except ImportError: - exc = Exception - else: - exc = getattr(module, name.strip()) - - elif hasattr(builtins, exc): - exc = getattr(builtins, exc) - - if not isinstance(exc, type) or not issubclass(exc, Exception): - self.data = None - return # not an actual exception - - msg = "An error has occurred and has been logged." - if not botconfig.PASTEBIN_ERRORS or botconfig.CHANNEL != botconfig.DEV_CHANNEL: - self.cli.msg(botconfig.CHANNEL, msg) - if botconfig.PASTEBIN_ERRORS and botconfig.DEV_CHANNEL: - try: - with socket.socket() as sock: - sock.connect(("termbin.com", 9999)) - sock.send(b"".join(s.encode("utf-8", "replace") for s in self.data) + b"\n") - url = sock.recv(1024).decode("utf-8") - except socket.error: - self.target_logger(self.data, display=False) - else: - self.cli.msg(botconfig.DEV_CHANNEL, " ".join((msg, url))) - self.data = None - -sys.stderr = ErrorHandler(buffer=buffer, encoding=sys.stderr.encoding, - errors=sys.stderr.errors, line_buffering=sys.stderr.line_buffering) diff --git a/src/decorators.py b/src/decorators.py index bb3e35d..15ca257 100644 --- a/src/decorators.py +++ b/src/decorators.py @@ -1,6 +1,9 @@ +import traceback import fnmatch +import socket from collections import defaultdict +from oyoyo.client import IRCClient from oyoyo.parse import parse_nick import botconfig @@ -9,10 +12,53 @@ from src.utilities import * from src import logger adminlog = logger("audit.log") +errlog = logger("errors.log") COMMANDS = defaultdict(list) HOOKS = defaultdict(list) +# Error handler decorators + +class handle_error: + def __init__(self, func): + self.func = func + self.instance = None + self.owner = object + + def __get__(self, instance, owner): + self.instance = instance + self.owner = owner + return self + + def __call__(self, *args, **kwargs): + try: + self.func.__get__(self.instance, self.owner)(*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 cli is not None: + msg = "An error has occurred and has been logged." + if not botconfig.PASTEBIN_ERRORS or botconfig.CHANNEL != botconfig.DEV_CHANNEL: + cli.msg(botconfig.CHANNEL, msg) + if botconfig.PASTEBIN_ERRORS and botconfig.DEV_CHANNEL: + try: + with socket.socket() as sock: + sock.connect(("termbin.com", 9999)) + sock.send(traceback.format_exc().encode("utf-8", "replace") + b"\n") + url = sock.recv(1024).decode("utf-8") + except socket.error: + pass + else: + cli.msg(botconfig.DEV_CHANNEL, " ".join((msg, url))) + class cmd: def __init__(self, *cmds, raw_nick=False, admin_only=False, owner_only=False, chan=True, pm=False, playing=False, silenced=False, phases=(), roles=()): @@ -49,6 +95,7 @@ class cmd: self.__doc__ = self.func.__doc__ return self + @handle_error def caller(self, *args): largs = list(args) @@ -190,6 +237,10 @@ class hook: self.__doc__ = self.func.__doc__ return self + @handle_error + def caller(self, *args, **kwargs): + return self.func(*args, **kwargs) + @staticmethod def unhook(hookid): for each in list(HOOKS): diff --git a/src/handler.py b/src/handler.py index 14693c5..5963ea0 100644 --- a/src/handler.py +++ b/src/handler.py @@ -14,8 +14,6 @@ from src import decorators, logger, wolfgame log = logger("errors.log") alog = logger(None) -sys.stderr.target_logger = log - hook = decorators.hook def on_privmsg(cli, rawnick, chan, msg, notice = False): @@ -48,12 +46,7 @@ def on_privmsg(cli, rawnick, chan, msg, notice = False): chan = parse_nick(rawnick)[0] for fn in decorators.COMMANDS[""]: - try: - fn.caller(cli, rawnick, chan, msg) - except Exception: - sys.stderr.write(traceback.format_exc()) - if botconfig.DEBUG_MODE: - raise + fn.caller(cli, rawnick, chan, msg) for x in list(decorators.COMMANDS.keys()): @@ -67,12 +60,7 @@ def on_privmsg(cli, rawnick, chan, msg, notice = False): continue if not h or h[0] == " ": for fn in decorators.COMMANDS.get(x, []): - try: - fn.caller(cli, rawnick, chan, h.lstrip()) - except Exception: - sys.stderr.write(traceback.format_exc()) - if botconfig.DEBUG_MODE: - raise + fn.caller(cli, rawnick, chan, h.lstrip()) def unhandled(cli, prefix, cmd, *args): @@ -81,12 +69,7 @@ def unhandled(cli, prefix, cmd, *args): for i,arg in enumerate(largs): if isinstance(arg, bytes): largs[i] = arg.decode('ascii') for fn in decorators.HOOKS.get(cmd, []): - try: - fn.func(cli, prefix, *largs) - except Exception: - sys.stderr.write(traceback.format_exc()) - if botconfig.DEBUG_MODE: - raise + fn.caller(cli, prefix, *largs) def connect_callback(cli): @hook("endofmotd", hookid=294) @@ -172,9 +155,6 @@ def connect_callback(cli): # capability but now claims otherwise. alog("Server refused capabilities: {0}".format(" ".join(caps))) - if sys.stderr.cli is None: - sys.stderr.cli = cli # first connection - if botconfig.SASL_AUTHENTICATION: @hook("authenticate") def auth_plus(cli, something, plus): diff --git a/src/wolfgame.py b/src/wolfgame.py index e6aa5bd..d9858b4 100644 --- a/src/wolfgame.py +++ b/src/wolfgame.py @@ -60,6 +60,7 @@ is_owner = var.is_owner cmd = decorators.cmd hook = decorators.hook +handle_error = decorators.handle_error COMMANDS = decorators.COMMANDS # Game Logic Begins: @@ -918,6 +919,7 @@ def toggle_altpinged_status(nick, value, old=None): var.PING_IF_NUMS[old].discard(host) var.PING_IF_NUMS[old].discard(hostmask) +@handle_error def join_timer_handler(cli): with var.WARNING_LOCK: var.PINGING_IFS = True @@ -1267,6 +1269,7 @@ def join_player(cli, player, chan, who = None, forced = False): return True +@handle_error def kill_join(cli, chan): pl = var.list_players() pl.sort(key=lambda x: x.lower()) @@ -1995,6 +1998,7 @@ def stats(cli, nick, chan, rest): var.PHASE) reply(cli, nick, chan, stats_mssg) +@handle_error def hurry_up(cli, gameid, change): if var.PHASE != "day": return if gameid: @@ -2764,6 +2768,7 @@ def chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ldemoniacs, stop_game(cli, winner, additional_winners=event.data["additional_winners"]) return True +@handle_error def del_player(cli, nick, forced_death=False, devoice=True, end_game=True, death_triggers=True, killer_role="", deadlist=[], original="", cmode=[], deadchat=[], ismain=True): """ Returns: False if one side won. @@ -3212,7 +3217,7 @@ def del_player(cli, nick, forced_death=False, devoice=True, end_game=True, death return ret - +@handle_error def reaper(cli, gameid): # check to see if idlers need to be killed. var.IDLE_WARNED = set() @@ -3414,6 +3419,7 @@ def fgoat(cli, nick, chan, rest): cli.msg(chan, "\u0002{0}\u0002's goat walks by and {1} \u0002{2}\u0002.".format(nick, goatact, togoat)) +@handle_error def return_to_village(cli, chan, nick, show_message): with var.GRAVEYARD_LOCK: if nick in var.DISCONNECTED.keys(): @@ -3849,6 +3855,7 @@ def begin_day(cli): event = Event("begin_day", {}) event.dispatch(cli, var) +@handle_error def night_warn(cli, gameid): if gameid != var.NIGHT_ID: return @@ -7459,7 +7466,7 @@ def cgamemode(cli, arg): else: cli.msg(chan, "Mode \u0002{0}\u0002 not found.".format(modeargs[0])) - +@handle_error def expire_start_votes(cli, chan): # Should never happen as the timer is removed on game start, but just to be safe if var.PHASE != 'join':