diff options
Diffstat (limited to '')
| -rw-r--r-- | bot/__main__.py | 5 | ||||
| -rw-r--r-- | bot/api.py | 77 | 
2 files changed, 80 insertions, 2 deletions
| diff --git a/bot/__main__.py b/bot/__main__.py index 4bc7d1202..23bfe03bf 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -6,11 +6,11 @@ from aiohttp import AsyncResolver, ClientSession, TCPConnector  from discord import Game  from discord.ext.commands import Bot, when_mentioned_or -from bot.api import APIClient +from bot.api import APIClient, APILoggingHandler  from bot.constants import Bot as BotConfig, DEBUG_MODE -log = logging.getLogger(__name__) +log = logging.getLogger('bot')  bot = Bot(      command_prefix=when_mentioned_or(BotConfig.prefix), @@ -29,6 +29,7 @@ bot.http_session = ClientSession(      )  )  bot.api_client = APIClient(loop=asyncio.get_event_loop()) +log.addHandler(APILoggingHandler(bot.api_client))  # Internal/debug  bot.load_extension("bot.cogs.error_handler") diff --git a/bot/api.py b/bot/api.py index e926a262e..6ac7ddb95 100644 --- a/bot/api.py +++ b/bot/api.py @@ -1,9 +1,13 @@ +import asyncio +import logging  from urllib.parse import quote as quote_url  import aiohttp  from .constants import Keys, URLs +log = logging.getLogger(__name__) +  class ResponseCodeError(ValueError):      def __init__(self, response: aiohttp.ClientResponse): @@ -58,3 +62,76 @@ class APIClient:              self.maybe_raise_for_status(resp, raise_for_status)              return await resp.json() + + +def loop_is_running() -> bool: +    # asyncio does not have a way to say "call this when the event +    # loop is running", see e.g. `callWhenRunning` from twisted. + +    try: +        asyncio.get_running_loop() +    except RuntimeError: +        return False +    return True + + +class APILoggingHandler(logging.StreamHandler): +    def __init__(self, client: APIClient): +        logging.StreamHandler.__init__(self) +        self.client = client + +        # internal batch of shipoff tasks that must not be scheduled +        # on the event loop yet - scheduled when the event loop is ready. +        self.queue = [] + +    async def ship_off(self, payload: dict): +        try: +            await self.client.post('logs', json=payload) +        except ResponseCodeError as err: +            log.warning( +                "Cannot send logging record to the site, got code %d.", +                err.response.status, +                extra={'via_handler': True} +            ) +        except Exception as err: +            log.warning( +                "Cannot send logging record to the site: %r", +                err, +                extra={'via_handler': True} +            ) + +    def emit(self, record: logging.LogRecord): +        # Ignore logging messages which are sent by this logging handler +        # itself. This is required because if we were to not ignore +        # messages emitted by this handler, we would infinitely recurse +        # back down into this logging handler, making the reactor run +        # like crazy, and eventually OOM something. Let's not do that... +        if not record.__dict__.get('via_handler'): +            payload = { +                'application': 'bot', +                'logger_name': record.name, +                'level': record.levelname.lower(), +                'module': record.module, +                'line': record.lineno, +                'message': self.format(record) +            } + +            task = self.ship_off(payload) +            if not loop_is_running(): +                self.queue.append(task) +            else: +                asyncio.create_task(task) +                self.schedule_queued_tasks() + +    def schedule_queued_tasks(self): +        for task in self.queue: +            asyncio.create_task(task) + +        if self.queue: +            log.debug( +                "Scheduled %d pending logging tasks.", +                len(self.queue), +                extra={'via_handler': True} +            ) + +        self.queue.clear() | 
