From 0d7bbafe9bf18ef9d1dda65bab5a5ae699d97d72 Mon Sep 17 00:00:00 2001 From: Numerlor <25886452+Numerlor@users.noreply.github.com> Date: Mon, 6 Sep 2021 23:48:34 +0200 Subject: Move logging to a separate module --- bot/__init__.py | 63 ++------------------------------------------------------- 1 file changed, 2 insertions(+), 61 deletions(-) (limited to 'bot/__init__.py') diff --git a/bot/__init__.py b/bot/__init__.py index c6a48105..b64f4732 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -6,80 +6,21 @@ except ModuleNotFoundError: pass import asyncio -import logging -import logging.handlers import os from functools import partial, partialmethod -from pathlib import Path import arrow from discord.ext import commands +from bot import log from bot.command import Command -from bot.constants import Client from bot.group import Group - -# Configure the "TRACE" logging level (e.g. "log.trace(message)") -logging.TRACE = 5 -logging.addLevelName(logging.TRACE, "TRACE") - - -def monkeypatch_trace(self: logging.Logger, msg: str, *args, **kwargs) -> None: - """ - Log 'msg % args' with severity 'TRACE'. - - To pass exception information, use the keyword argument exc_info with a true value, e.g. - logger.trace("Houston, we have an %s", "interesting problem", exc_info=1) - """ - if self.isEnabledFor(logging.TRACE): - self._log(logging.TRACE, msg, args, **kwargs) - - -logging.Logger.trace = monkeypatch_trace +log.setup() # Set timestamp of when execution started (approximately) start_time = arrow.utcnow() -# Set up file logging -log_dir = Path("bot/log") -log_file = log_dir / "hackbot.log" -os.makedirs(log_dir, exist_ok=True) - -# File handler rotates logs every 5 MB -file_handler = logging.handlers.RotatingFileHandler( - log_file, maxBytes=5 * (2**20), backupCount=10, encoding="utf-8", -) -file_handler.setLevel(logging.TRACE if Client.debug else logging.DEBUG) - -# Console handler prints to terminal -console_handler = logging.StreamHandler() -level = logging.TRACE if Client.debug else logging.INFO -console_handler.setLevel(level) - -# Remove old loggers, if any -root = logging.getLogger() -if root.handlers: - for handler in root.handlers: - root.removeHandler(handler) - -# Silence irrelevant loggers -logging.getLogger("discord").setLevel(logging.ERROR) -logging.getLogger("websockets").setLevel(logging.ERROR) -logging.getLogger("PIL").setLevel(logging.ERROR) -logging.getLogger("matplotlib").setLevel(logging.ERROR) -logging.getLogger("async_rediscache").setLevel(logging.WARNING) - -# Setup new logging configuration -logging.basicConfig( - format="%(asctime)s - %(name)s %(levelname)s: %(message)s", - datefmt="%D %H:%M:%S", - level=logging.TRACE if Client.debug else logging.DEBUG, - handlers=[console_handler, file_handler], -) -logging.getLogger().info("Logging initialization complete") - - # On Windows, the selector event loop is required for aiodns. if os.name == "nt": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) -- cgit v1.2.3 From 6fbd7883fee19e62898eacea08b4d7b7f7fc74db Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Mon, 4 Oct 2021 20:26:59 +0100 Subject: Monkey patch http.send_typing to catch 403s Sometimes discord turns off typing events by throwing 403's, so we should catch those --- bot/__init__.py | 26 +++++------------ bot/command.py | 18 ------------ bot/group.py | 18 ------------ bot/monkey_patches.py | 78 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 55 deletions(-) delete mode 100644 bot/command.py delete mode 100644 bot/group.py create mode 100644 bot/monkey_patches.py (limited to 'bot/__init__.py') diff --git a/bot/__init__.py b/bot/__init__.py index c6a48105..db576cb2 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -15,28 +15,15 @@ from pathlib import Path import arrow from discord.ext import commands -from bot.command import Command +from bot import monkey_patches from bot.constants import Client -from bot.group import Group # Configure the "TRACE" logging level (e.g. "log.trace(message)") logging.TRACE = 5 logging.addLevelName(logging.TRACE, "TRACE") - -def monkeypatch_trace(self: logging.Logger, msg: str, *args, **kwargs) -> None: - """ - Log 'msg % args' with severity 'TRACE'. - - To pass exception information, use the keyword argument exc_info with a true value, e.g. - logger.trace("Houston, we have an %s", "interesting problem", exc_info=1) - """ - if self.isEnabledFor(logging.TRACE): - self._log(logging.TRACE, msg, args, **kwargs) - - -logging.Logger.trace = monkeypatch_trace +logging.Logger.trace = monkey_patches.trace_log # Set timestamp of when execution started (approximately) start_time = arrow.utcnow() @@ -84,11 +71,12 @@ logging.getLogger().info("Logging initialization complete") if os.name == "nt": asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy()) +monkey_patches.patch_typing() # Monkey-patch discord.py decorators to use the both the Command and Group subclasses which supports root aliases. # Must be patched before any cogs are added. -commands.command = partial(commands.command, cls=Command) -commands.GroupMixin.command = partialmethod(commands.GroupMixin.command, cls=Command) +commands.command = partial(commands.command, cls=monkey_patches.Command) +commands.GroupMixin.command = partialmethod(commands.GroupMixin.command, cls=monkey_patches.Command) -commands.group = partial(commands.group, cls=Group) -commands.GroupMixin.group = partialmethod(commands.GroupMixin.group, cls=Group) +commands.group = partial(commands.group, cls=monkey_patches.Group) +commands.GroupMixin.group = partialmethod(commands.GroupMixin.group, cls=monkey_patches.Group) diff --git a/bot/command.py b/bot/command.py deleted file mode 100644 index 0fb900f7..00000000 --- a/bot/command.py +++ /dev/null @@ -1,18 +0,0 @@ -from discord.ext import commands - - -class Command(commands.Command): - """ - A `discord.ext.commands.Command` subclass which supports root aliases. - - A `root_aliases` keyword argument is added, which is a sequence of alias names that will act as - top-level commands rather than being aliases of the command's group. It's stored as an attribute - also named `root_aliases`. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.root_aliases = kwargs.get("root_aliases", []) - - if not isinstance(self.root_aliases, (list, tuple)): - raise TypeError("Root aliases of a command must be a list or a tuple of strings.") diff --git a/bot/group.py b/bot/group.py deleted file mode 100644 index a7bc59b7..00000000 --- a/bot/group.py +++ /dev/null @@ -1,18 +0,0 @@ -from discord.ext import commands - - -class Group(commands.Group): - """ - A `discord.ext.commands.Group` subclass which supports root aliases. - - A `root_aliases` keyword argument is added, which is a sequence of alias names that will act as - top-level groups rather than being aliases of the command's group. It's stored as an attribute - also named `root_aliases`. - """ - - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - self.root_aliases = kwargs.get("root_aliases", []) - - if not isinstance(self.root_aliases, (list, tuple)): - raise TypeError("Root aliases of a group must be a list or a tuple of strings.") diff --git a/bot/monkey_patches.py b/bot/monkey_patches.py new file mode 100644 index 00000000..fe81f2e3 --- /dev/null +++ b/bot/monkey_patches.py @@ -0,0 +1,78 @@ +import logging +from datetime import datetime, timedelta + +from discord import Forbidden, http +from discord.ext import commands + +log = logging.getLogger(__name__) + + +def trace_log(self: logging.Logger, msg: str, *args, **kwargs) -> None: + """ + Log 'msg % args' with severity 'TRACE'. + + To pass exception information, use the keyword argument exc_info with a true value, e.g. + logger.trace("Houston, we have an %s", "interesting problem", exc_info=1) + """ + if self.isEnabledFor(logging.TRACE): + self._log(logging.TRACE, msg, args, **kwargs) + + +class Command(commands.Command): + """ + A `discord.ext.commands.Command` subclass which supports root aliases. + + A `root_aliases` keyword argument is added, which is a sequence of alias names that will act as + top-level commands rather than being aliases of the command's group. It's stored as an attribute + also named `root_aliases`. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.root_aliases = kwargs.get("root_aliases", []) + + if not isinstance(self.root_aliases, (list, tuple)): + raise TypeError("Root aliases of a command must be a list or a tuple of strings.") + + +class Group(commands.Group): + """ + A `discord.ext.commands.Group` subclass which supports root aliases. + + A `root_aliases` keyword argument is added, which is a sequence of alias names that will act as + top-level groups rather than being aliases of the command's group. It's stored as an attribute + also named `root_aliases`. + """ + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.root_aliases = kwargs.get("root_aliases", []) + + if not isinstance(self.root_aliases, (list, tuple)): + raise TypeError("Root aliases of a group must be a list or a tuple of strings.") + + +def patch_typing() -> None: + """ + Sometimes discord turns off typing events by throwing 403's. + + Handle those issues by patching the trigger_typing method so it ignores 403's in general. + """ + log.debug("Patching send_typing, which should fix things breaking when discord disables typing events. Stay safe!") + + original = http.HTTPClient.send_typing + last_403 = None + + async def honeybadger_type(self, channel_id: int) -> None: # noqa: ANN001 + nonlocal last_403 + if last_403 and (datetime.utcnow() - last_403) < timedelta(minutes=5): + log.warning("Not sending typing event, we got a 403 less than 5 minutes ago.") + return + try: + await original(self, channel_id) + except Forbidden: + last_403 = datetime.utcnow() + log.warning("Got a 403 from typing event!") + pass + + http.HTTPClient.send_typing = honeybadger_type -- cgit v1.2.3 From cdaa77830f9bce1529d93990f00415dbde33a0cd Mon Sep 17 00:00:00 2001 From: Matteo Bertucci Date: Fri, 22 Oct 2021 07:25:39 +0000 Subject: Isort: give the codebase a sort --- bot/__init__.py | 1 - bot/exts/core/help.py | 5 +---- bot/exts/core/internal_eval/_internal_eval.py | 1 + bot/exts/events/advent_of_code/_cog.py | 4 +--- bot/exts/holidays/easter/earth_photos.py | 3 +-- bot/exts/holidays/halloween/scarymovie.py | 1 + bot/exts/utilities/issues.py | 9 +-------- bot/utils/checks.py | 9 +-------- bot/utils/halloween/spookifications.py | 3 +-- 9 files changed, 8 insertions(+), 28 deletions(-) (limited to 'bot/__init__.py') diff --git a/bot/__init__.py b/bot/__init__.py index db576cb2..cfaee9f8 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -18,7 +18,6 @@ from discord.ext import commands from bot import monkey_patches from bot.constants import Client - # Configure the "TRACE" logging level (e.g. "log.trace(message)") logging.TRACE = 5 logging.addLevelName(logging.TRACE, "TRACE") diff --git a/bot/exts/core/help.py b/bot/exts/core/help.py index 4b766b50..db3c2aa6 100644 --- a/bot/exts/core/help.py +++ b/bot/exts/core/help.py @@ -13,10 +13,7 @@ from rapidfuzz import process from bot import constants from bot.bot import Bot from bot.constants import Emojis -from bot.utils.pagination import ( - FIRST_EMOJI, LAST_EMOJI, - LEFT_EMOJI, LinePaginator, RIGHT_EMOJI, -) +from bot.utils.pagination import FIRST_EMOJI, LAST_EMOJI, LEFT_EMOJI, LinePaginator, RIGHT_EMOJI DELETE_EMOJI = Emojis.trashcan diff --git a/bot/exts/core/internal_eval/_internal_eval.py b/bot/exts/core/internal_eval/_internal_eval.py index 4f6b4321..12a860fa 100644 --- a/bot/exts/core/internal_eval/_internal_eval.py +++ b/bot/exts/core/internal_eval/_internal_eval.py @@ -10,6 +10,7 @@ from bot.bot import Bot from bot.constants import Client, Roles from bot.utils.decorators import with_role from bot.utils.extensions import invoke_help_command + from ._helpers import EvalContext __all__ = ["InternalEval"] diff --git a/bot/exts/events/advent_of_code/_cog.py b/bot/exts/events/advent_of_code/_cog.py index 7dd967ec..2c1f4541 100644 --- a/bot/exts/events/advent_of_code/_cog.py +++ b/bot/exts/events/advent_of_code/_cog.py @@ -9,9 +9,7 @@ import discord from discord.ext import commands from bot.bot import Bot -from bot.constants import ( - AdventOfCode as AocConfig, Channels, Colours, Emojis, Month, Roles, WHITELISTED_CHANNELS, -) +from bot.constants import AdventOfCode as AocConfig, Channels, Colours, Emojis, Month, Roles, WHITELISTED_CHANNELS from bot.exts.events.advent_of_code import _helpers from bot.exts.events.advent_of_code.views.dayandstarview import AoCDropdownView from bot.utils.decorators import InChannelCheckFailure, in_month, whitelist_override, with_role diff --git a/bot/exts/holidays/easter/earth_photos.py b/bot/exts/holidays/easter/earth_photos.py index f65790af..27442f1c 100644 --- a/bot/exts/holidays/easter/earth_photos.py +++ b/bot/exts/holidays/easter/earth_photos.py @@ -4,8 +4,7 @@ import discord from discord.ext import commands from bot.bot import Bot -from bot.constants import Colours -from bot.constants import Tokens +from bot.constants import Colours, Tokens log = logging.getLogger(__name__) diff --git a/bot/exts/holidays/halloween/scarymovie.py b/bot/exts/holidays/halloween/scarymovie.py index 33659fd8..89310b97 100644 --- a/bot/exts/holidays/halloween/scarymovie.py +++ b/bot/exts/holidays/halloween/scarymovie.py @@ -6,6 +6,7 @@ from discord.ext import commands from bot.bot import Bot from bot.constants import Tokens + log = logging.getLogger(__name__) diff --git a/bot/exts/utilities/issues.py b/bot/exts/utilities/issues.py index 36655e1b..b6d5a43e 100644 --- a/bot/exts/utilities/issues.py +++ b/bot/exts/utilities/issues.py @@ -9,14 +9,7 @@ from discord.ext import commands from bot.bot import Bot from bot.constants import ( - Categories, - Channels, - Colours, - ERROR_REPLIES, - Emojis, - NEGATIVE_REPLIES, - Tokens, - WHITELISTED_CHANNELS + Categories, Channels, Colours, ERROR_REPLIES, Emojis, NEGATIVE_REPLIES, Tokens, WHITELISTED_CHANNELS ) from bot.utils.decorators import whitelist_override from bot.utils.extensions import invoke_help_command diff --git a/bot/utils/checks.py b/bot/utils/checks.py index 612d1ed6..8c426ed7 100644 --- a/bot/utils/checks.py +++ b/bot/utils/checks.py @@ -4,14 +4,7 @@ from collections.abc import Container, Iterable from typing import Callable, Optional from discord.ext.commands import ( - BucketType, - CheckFailure, - Cog, - Command, - CommandOnCooldown, - Context, - Cooldown, - CooldownMapping, + BucketType, CheckFailure, Cog, Command, CommandOnCooldown, Context, Cooldown, CooldownMapping ) from bot import constants diff --git a/bot/utils/halloween/spookifications.py b/bot/utils/halloween/spookifications.py index 93c5ddb9..c45ef8dc 100644 --- a/bot/utils/halloween/spookifications.py +++ b/bot/utils/halloween/spookifications.py @@ -1,8 +1,7 @@ import logging from random import choice, randint -from PIL import Image -from PIL import ImageOps +from PIL import Image, ImageOps log = logging.getLogger() -- cgit v1.2.3 From 40b58132d488bab962d8aeecfc932a1cb5842a85 Mon Sep 17 00:00:00 2001 From: Hassan Abouelela Date: Fri, 12 Nov 2021 06:19:59 +0400 Subject: Move Sentry Into Init Moves the sentry setup to be one of the very first things run during startup, so we are able to catch more errors, such as ones that might occur while setting up logs. Signed-off-by: Hassan Abouelela --- bot/__init__.py | 18 ++++++++++++++++++ bot/__main__.py | 20 +------------------- bot/constants.py | 3 --- 3 files changed, 19 insertions(+), 22 deletions(-) (limited to 'bot/__init__.py') diff --git a/bot/__init__.py b/bot/__init__.py index b19bd76a..ae53a5a5 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -6,14 +6,32 @@ except ModuleNotFoundError: pass import asyncio +import logging import os from functools import partial, partialmethod import arrow +import sentry_sdk from discord.ext import commands +from sentry_sdk.integrations.logging import LoggingIntegration +from sentry_sdk.integrations.redis import RedisIntegration from bot import log, monkey_patches +sentry_logging = LoggingIntegration( + level=logging.DEBUG, + event_level=logging.WARNING +) + +sentry_sdk.init( + dsn=os.environ.get("BOT_SENTRY_DSN"), + integrations=[ + sentry_logging, + RedisIntegration() + ], + release=f"sir-lancebot@{os.environ.get('GIT_SHA', 'foobar')}" +) + log.setup() # Set timestamp of when execution started (approximately) diff --git a/bot/__main__.py b/bot/__main__.py index c6e5fa57..6889fe2b 100644 --- a/bot/__main__.py +++ b/bot/__main__.py @@ -1,28 +1,10 @@ import logging -import sentry_sdk -from sentry_sdk.integrations.logging import LoggingIntegration -from sentry_sdk.integrations.redis import RedisIntegration - from bot.bot import bot -from bot.constants import Client, GIT_SHA, STAFF_ROLES, WHITELISTED_CHANNELS +from bot.constants import Client, STAFF_ROLES, WHITELISTED_CHANNELS from bot.utils.decorators import whitelist_check from bot.utils.extensions import walk_extensions -sentry_logging = LoggingIntegration( - level=logging.DEBUG, - event_level=logging.WARNING -) - -sentry_sdk.init( - dsn=Client.sentry_dsn, - integrations=[ - sentry_logging, - RedisIntegration() - ], - release=f"sir-lancebot@{GIT_SHA}" -) - log = logging.getLogger(__name__) bot.add_check(whitelist_check(channels=WHITELISTED_CHANNELS, roles=STAFF_ROLES)) diff --git a/bot/constants.py b/bot/constants.py index 9d12000e..2b41b8a4 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -134,7 +134,6 @@ class Client(NamedTuple): guild = int(environ.get("BOT_GUILD", 267624335836053506)) prefix = environ.get("PREFIX", ".") token = environ.get("BOT_TOKEN") - sentry_dsn = environ.get("BOT_SENTRY_DSN") debug = environ.get("BOT_DEBUG", "true").lower() == "true" github_bot_repo = "https://github.com/python-discord/sir-lancebot" # Override seasonal locks: 1 (January) to 12 (December) @@ -348,8 +347,6 @@ WHITELISTED_CHANNELS = ( Channels.voice_chat_1, ) -GIT_SHA = environ.get("GIT_SHA", "foobar") - # Bot replies ERROR_REPLIES = [ "Please don't do that.", -- cgit v1.2.3 From 163a6e14c22b7992f44dca699de9e71c11362613 Mon Sep 17 00:00:00 2001 From: Chris Lovering Date: Sun, 5 Dec 2021 13:11:03 +0000 Subject: Patch d.py's message converters to infer channelID from the given context Discord.py's message converter is supposed to infer channelID based on ctx.channel if only a messageID is given. A refactor (linked below) a few weeks before d.py's archival broke this, so that if only a messageID is given to the converter, it will only find that message if it's in the bot's cache. --- bot/__init__.py | 5 +++++ bot/monkey_patches.py | 24 ++++++++++++++++++++++++ 2 files changed, 29 insertions(+) (limited to 'bot/__init__.py') diff --git a/bot/__init__.py b/bot/__init__.py index ae53a5a5..3136c863 100644 --- a/bot/__init__.py +++ b/bot/__init__.py @@ -43,6 +43,11 @@ if os.name == "nt": monkey_patches.patch_typing() +# This patches any convertors that use PartialMessage, but not the PartialMessageConverter itself +# as library objects are made by this mapping. +# https://github.com/Rapptz/discord.py/blob/1a4e73d59932cdbe7bf2c281f25e32529fc7ae1f/discord/ext/commands/converter.py#L984-L1004 +commands.converter.PartialMessageConverter = monkey_patches.FixedPartialMessageConverter + # Monkey-patch discord.py decorators to use the both the Command and Group subclasses which supports root aliases. # Must be patched before any cogs are added. commands.command = partial(commands.command, cls=monkey_patches.Command) diff --git a/bot/monkey_patches.py b/bot/monkey_patches.py index fa6627d1..19965c19 100644 --- a/bot/monkey_patches.py +++ b/bot/monkey_patches.py @@ -1,10 +1,12 @@ import logging +import re from datetime import datetime, timedelta from discord import Forbidden, http from discord.ext import commands log = logging.getLogger(__name__) +MESSAGE_ID_RE = re.compile(r'(?P[0-9]{15,20})$') class Command(commands.Command): @@ -65,3 +67,25 @@ def patch_typing() -> None: pass http.HTTPClient.send_typing = honeybadger_type + + +class FixedPartialMessageConverter(commands.PartialMessageConverter): + """ + Make the Message converter infer channelID from the given context if only a messageID is given. + + Discord.py's Message converter is supposed to infer channelID based + on ctx.channel if only a messageID is given. A refactor commit, linked below, + a few weeks before d.py's archival broke this defined behaviour of the converter. + Currently, if only a messageID is given to the converter, it will only find that message + if it's in the bot's cache. + + https://github.com/Rapptz/discord.py/commit/1a4e73d59932cdbe7bf2c281f25e32529fc7ae1f + """ + + @staticmethod + def _get_id_matches(ctx: commands.Context, argument: str) -> tuple[int, int, int]: + """Inserts ctx.channel.id before calling super method if argument is just a messageID.""" + match = MESSAGE_ID_RE.match(argument) + if match: + argument = f"{ctx.channel.id}-{match.group('message_id')}" + return commands.PartialMessageConverter._get_id_matches(ctx, argument) -- cgit v1.2.3