Properly fix the error handler

This commit is contained in:
Vgr E. Barry 2015-11-04 12:41:47 -05:00
parent 154589a748
commit b28d4bf6e3
4 changed files with 63 additions and 104 deletions

View File

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

View File

@ -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):

View File

@ -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
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
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):

View File

@ -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':