def _log_task_exception( task: asyncio.Task, *, suppressed_exceptions: t.Tuple[t.Type[Exception]]) -> None: """Retrieve and log the exception raised in `task` if one exists.""" with contextlib.suppress(asyncio.CancelledError): exception = task.exception() # Log the exception if one exists. if exception and not isinstance(exception, suppressed_exceptions): log = get_logger(__name__) log.error(f"Error in task {task.get_name()} {id(task)}!", exc_info=exception)
def assertNotLogs(self, logger=None, level=None, msg=None): # noqa: N802 """ Asserts that no logs of `level` and higher were emitted by `logger`. You can specify a specific `logger`, the minimum `logging` level we want to watch and a custom `msg` to be added to the `AssertionError` if thrown. If the assertion fails, the recorded log records will be outputted with the `AssertionError` message. The context manager does not yield a live `look` into the logging records, since we use this context manager when we're testing under the assumption that no log records will be emitted. """ if not isinstance(logger, logging.Logger): logger = get_logger(logger) if level: level = logging._nameToLevel.get(level, level) else: level = logging.INFO handler = _CaptureLogHandler() old_handlers = logger.handlers[:] old_level = logger.level old_propagate = logger.propagate logger.handlers = [handler] logger.setLevel(level) logger.propagate = False try: yield except Exception as exc: raise exc finally: logger.handlers = old_handlers logger.propagate = old_propagate logger.setLevel(old_level) if handler.records: level_name = logging.getLevelName(level) n_logs = len(handler.records) base_message = f"{n_logs} logs of {level_name} or higher were triggered on {logger.name}:\n" records = [str(record) for record in handler.records] record_message = "\n".join(records) standard_message = self._truncateMessage(base_message, record_message) msg = self._formatMessage(msg, standard_message) self.fail(msg)
import abc import typing as t from collections import namedtuple from discord import Guild from discord.ext.commands import Context from more_itertools import chunked import bot from bot.api import ResponseCodeError from bot.log import get_logger from bot.utils.members import get_or_fetch_member log = get_logger(__name__) CHUNK_SIZE = 1000 # These objects are declared as namedtuples because tuples are hashable, # something that we make use of when diffing site roles against guild roles. _Role = namedtuple('Role', ('id', 'name', 'colour', 'permissions', 'position')) _Diff = namedtuple('Diff', ('created', 'updated', 'deleted')) # Implementation of static abstract methods are not enforced if the subclass is never instantiated. # However, methods are kept abstract to at least symbolise that they should be abstract. class Syncer(abc.ABC): """Base class for synchronising the database with objects in the Discord cache.""" @staticmethod @property @abc.abstractmethod def name() -> str:
import warnings from collections import defaultdict from contextlib import suppress from typing import Dict, List, Optional import aiohttp import discord from async_rediscache import RedisSession from discord.ext import commands from sentry_sdk import push_scope from bot import api, constants from bot.async_stats import AsyncStatsClient from bot.log import get_logger log = get_logger('bot') LOCALHOST = "127.0.0.1" class StartupError(Exception): """Exception class for startup errors.""" def __init__(self, base: Exception): super().__init__() self.exception = base class Bot(commands.Bot): """A subclass of `discord.ext.commands.Bot` with an aiohttp session and an API client.""" def __init__(self, *args, redis_session: RedisSession, **kwargs):
def __init__(self, name: str): self.name = name self._log = get_logger(f"{__name__}.{name}") self._scheduled_tasks: t.Dict[t.Hashable, asyncio.Task] = {}
import aiohttp import bot from bot import constants from bot.bot import Bot, StartupError from bot.log import get_logger, setup_sentry setup_sentry() try: bot.instance = Bot.create() bot.instance.load_extensions() bot.instance.run(constants.Bot.token) except StartupError as e: message = "Unknown Startup Error Occurred." if isinstance(e.exception, (aiohttp.ClientConnectorError, aiohttp.ServerDisconnectedError)): message = "Could not connect to site API. Is it running?" elif isinstance(e.exception, OSError): message = "Could not connect to Redis. Is it running?" # The exception is logged with an empty message so the actual message is visible at the bottom log = get_logger("bot") log.fatal("", exc_info=e.exception) log.fatal(message) exit(69)
def test_logging_test_case_respects_alternative_logger(self): """Test if LoggingTestCase only checks the provided logger.""" log_one = get_logger("log one") log_two = get_logger("log two") with LoggingTestCase.assertNotLogs(self, logger=log_one): log_two.info("Hello, this should not raise an AssertionError")
def test_logging_test_case_works_with_logger_instance(self): """Test if the LoggingTestCase captures logging for provided logger.""" log = get_logger("new_logger") with self.assertRaises(AssertionError): with LoggingTestCase.assertNotLogs(self, logger=log): log.info("Hello, this should raise an AssertionError")
def setUpClass(cls): cls.log = get_logger(__name__)
import logging from bot.log import get_logger log = get_logger() log.setLevel(logging.CRITICAL)