diff --git a/settings.json.example b/settings.json.example index d2e9c14..a2688d2 100644 --- a/settings.json.example +++ b/settings.json.example @@ -19,21 +19,24 @@ "default": { "formatter": "standard", "class": "logging.StreamHandler", - "stream": "ext://sys.stdout" + "stream": "ext://sys.stdout", + "level": 0 }, "file": { "formatter": "standard", "class": "logging.handlers.TimedRotatingFileHandler", - "filename": "error.log", + "filename": "logs/error.log", "interval": 7, - "when": "D" + "when": "D", + "level": 25 } }, "loggers": { "": { "level": 0, "handlers": [ - "default" + "default", + "file" ] }, "rcgcdb.bot": {}, diff --git a/src/argparser.py b/src/argparser.py new file mode 100644 index 0000000..df93ce0 --- /dev/null +++ b/src/argparser.py @@ -0,0 +1,5 @@ +import argparse + +parser = argparse.ArgumentParser(description="Starts the bot to retrieve wiki recent changes.") +parser.add_argument("-d", "--debug", action='store_true', help="Starts debugging session, will cause exceptions to return immediately") +command_line_args = parser.parse_args() diff --git a/src/bot.py b/src/bot.py index 3aff1c5..a4fbfc2 100644 --- a/src/bot.py +++ b/src/bot.py @@ -3,10 +3,12 @@ import asyncio import logging.config import signal import sys +import traceback from collections import defaultdict import requests +from src.argparser import command_line_args from src.config import settings from src.database import db_cursor from src.exceptions import * @@ -14,12 +16,15 @@ from src.misc import get_paths from src.msgqueue import messagequeue from src.queue_handler import DBHandler from src.wiki import Wiki, process_cats, process_mwmsgs, essential_info -from src.discord import DiscordMessage +from src.discord import DiscordMessage, formatter_exception_logger logging.config.dictConfig(settings["logging"]) logger = logging.getLogger("rcgcdb.bot") logger.debug("Current settings: {settings}".format(settings=settings)) -logger.info("RcGcDb v.{} is starting up.".format("1.0")) +logger.info("RcGcDb v{} is starting up.".format("1.0")) + +if command_line_args.debug: + logger.info("Debug mode is active!") # Log Fail states with structure wiki_url: number of fail states all_wikis: dict = {} @@ -78,7 +83,7 @@ async def wiki_scanner(): wiki_response = await local_wiki.fetch_wiki(extended, db_wiki["wiki"], session) await local_wiki.check_status(db_wiki["wiki"], wiki_response.status) except (WikiServerError, WikiError): - logger.exception("Exeption when fetching the wiki") + logger.error("Exeption when fetching the wiki") continue # ignore this wiki if it throws errors try: recent_changes_resp = await wiki_response.json() @@ -114,8 +119,14 @@ async def wiki_scanner(): for change in recent_changes: # Yeah, second loop since the categories require to be all loaded up if change["rcid"] > db_wiki["rcid"]: for target in targets.items(): - await essential_info(change, categorize_events, local_wiki, db_wiki, target, paths, + try: + await essential_info(change, categorize_events, local_wiki, db_wiki, target, paths, recent_changes_resp) + except: + if command_line_args.debug: + raise # reraise the issue + else: + await formatter_exception_logger(db_wiki["wiki"], change, traceback.format_exc()) if recent_changes: DBHandler.add(db_wiki["wiki"], change["rcid"]) DBHandler.update_db() @@ -143,7 +154,10 @@ def global_exception_handler(loop, context): """Global exception handler for asyncio, lets us know when something crashes""" msg = context.get("exception", context["message"]) logger.error("Global exception handler: {}".format(msg)) - requests.post("https://discord.com/api/webhooks/"+settings["monitoring_webhook"], data=repr(DiscordMessage("compact", "monitoring", [settings["monitoring_webhook"]], wiki=None, content="[RcGcDb] Global exception handler: {}".format(msg))), headers={'Content-Type': 'application/json'}) + if command_line_args.debug is False: + requests.post("https://discord.com/api/webhooks/"+settings["monitoring_webhook"], data=repr(DiscordMessage("compact", "monitoring", [settings["monitoring_webhook"]], wiki=None, content="[RcGcDb] Global exception handler: {}".format(msg))), headers={'Content-Type': 'application/json'}) + else: + shutdown(loop) async def main_loop(): loop = asyncio.get_event_loop() diff --git a/src/discord.py b/src/discord.py index 94bcb0a..6a4cdf8 100644 --- a/src/discord.py +++ b/src/discord.py @@ -6,6 +6,7 @@ from src.config import settings from src.database import db_cursor from src.i18n import langs from asyncio import TimeoutError + import aiohttp logger = logging.getLogger("rcgcdb.discord") @@ -105,6 +106,18 @@ async def wiki_removal_monitor(wiki_url, status): await send_to_discord_webhook_monitoring(DiscordMessage("compact", "webhook/remove", content="Removing {} because {}.".format(wiki_url, status), webhook_url=[None], wiki=None)) +async def formatter_exception_logger(wiki_url, change, exception): + """Creates a Discord message reporting a crash in RC formatter area""" + message = DiscordMessage("embed", "bot/exception", [None], wiki=None) + message["description"] = exception + message["title"] = "RC Exception Report" + change = change[0:1000] + message.add_field("Wiki URL", wiki_url) + message.add_field("Change", change) + message.finish_embed() + await send_to_discord_webhook_monitoring(message) + + async def send_to_discord_webhook_monitoring(data: DiscordMessage): header = settings["header"] header['Content-Type'] = 'application/json'