From 778ba387c87ea33c2ca70e970921ade5a82c3140 Mon Sep 17 00:00:00 2001 From: ChrisLovering Date: Mon, 6 Jul 2026 17:23:50 -0400 Subject: [PATCH 1/2] Add ruff rule for logging not using f-strings This is so that sentry errors get grouped together correctly --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index beb96859a7..1df1e46d53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -71,7 +71,7 @@ output-format = "concise" unsafe-fixes = true [tool.ruff.lint] -select = ["ANN", "B", "C4", "D", "DTZ", "E", "F", "I", "ISC", "INT", "N", "PGH", "PIE", "Q", "RET", "RSE", "RUF", "S", "SIM", "T20", "TID", "UP", "W"] +select = ["ANN", "B", "C4", "D", "DTZ", "E", "F", "G", "I", "ISC", "INT", "LOG", "N", "PGH", "PIE", "Q", "RET", "RSE", "RUF", "S", "SIM", "T20", "TID", "UP", "W"] ignore = [ "ANN002", "ANN003", "ANN204", "ANN206", "ANN401", "B904", From 4d91675bafe4c88ae021f1b736e9a18549efad1e Mon Sep 17 00:00:00 2001 From: ChrisLovering Date: Mon, 6 Jul 2026 17:23:59 -0400 Subject: [PATCH 2/2] remove all f-strings from logs --- bot/bot.py | 6 +- bot/decorators.py | 8 +-- bot/exts/backend/branding/_cog.py | 28 +++++---- bot/exts/backend/branding/_repository.py | 6 +- bot/exts/backend/config_verifier.py | 2 +- bot/exts/backend/error_handler.py | 8 +-- bot/exts/backend/sync/_syncers.py | 6 +- bot/exts/filtering/_filter_lists/antispam.py | 4 +- .../filtering/_filter_lists/filter_list.py | 2 +- .../_filters/unique/discord_token.py | 10 ++-- bot/exts/filtering/_settings.py | 9 +-- .../actions/infraction_and_notification.py | 12 ++-- .../_settings_types/actions/remove_context.py | 4 +- bot/exts/filtering/_ui/ui.py | 2 +- bot/exts/filtering/filtering.py | 36 +++++------ bot/exts/fun/off_topic_names.py | 22 +++---- bot/exts/help_channels/_channel.py | 8 +-- bot/exts/help_channels/_cog.py | 4 +- bot/exts/info/code_snippets.py | 6 +- bot/exts/info/codeblock/_cog.py | 4 +- bot/exts/info/doc/_batch_parser.py | 4 +- bot/exts/info/doc/_cog.py | 18 +++--- bot/exts/info/doc/_inventory_parser.py | 18 +++--- bot/exts/info/doc/_redis_cache.py | 12 ++-- bot/exts/info/pep.py | 4 +- bot/exts/info/python_news.py | 4 +- bot/exts/info/subscribe.py | 2 +- bot/exts/info/tags.py | 2 +- bot/exts/moderation/defcon.py | 8 +-- bot/exts/moderation/incidents.py | 32 +++++----- bot/exts/moderation/infraction/_scheduler.py | 59 +++++++++++-------- bot/exts/moderation/infraction/_utils.py | 14 ++--- bot/exts/moderation/infraction/infractions.py | 6 +- .../moderation/infraction/superstarify.py | 12 ++-- bot/exts/moderation/metabase.py | 2 +- bot/exts/moderation/silence.py | 36 +++++------ bot/exts/moderation/slowmode.py | 22 +++---- bot/exts/moderation/stream.py | 26 ++++---- .../moderation/watchchannels/_watchchannel.py | 8 +-- .../moderation/watchchannels/bigbrother.py | 4 +- bot/exts/recruitment/talentpool/_cog.py | 16 ++--- bot/exts/recruitment/talentpool/_review.py | 4 +- .../utils/attachment_pastebin_uploader.py | 2 +- bot/exts/utils/extensions.py | 6 +- bot/exts/utils/reminders.py | 24 ++++---- bot/exts/utils/snekbox/_cog.py | 13 ++-- bot/exts/utils/snekbox/_eval.py | 2 +- bot/utils/lock.py | 4 +- bot/utils/messages.py | 4 +- 49 files changed, 290 insertions(+), 265 deletions(-) diff --git a/bot/bot.py b/bot/bot.py index 35dbd1ba4e..c0b391c098 100644 --- a/bot/bot.py +++ b/bot/bot.py @@ -40,7 +40,7 @@ async def ping_services(self) -> None: attempts = 0 while True: try: - log.info(f"Attempting site connection: {attempts + 1}/{constants.URLs.connect_max_retries}") + log.info("Attempting site connection: %s/%s", attempts + 1, constants.URLs.connect_max_retries) await self.api_client.get("healthcheck") break @@ -55,7 +55,7 @@ async def setup_hook(self) -> None: await super().setup_hook() await self.load_extensions(exts) - async def on_error(self, event: str, *args, **kwargs) -> None: + async def on_error(self, event: str, /, *args, **kwargs) -> None: """Log errors raised in event listeners rather than printing them to stderr.""" e_val = exception() @@ -76,4 +76,4 @@ async def on_error(self, event: str, *args, **kwargs) -> None: scope.set_extra("args", args) scope.set_extra("kwargs", kwargs) - log.exception(f"Unhandled exception in {event}.") + log.exception("Unhandled exception in %s.", event) diff --git a/bot/decorators.py b/bot/decorators.py index d39c1cc33c..9be39953e4 100644 --- a/bot/decorators.py +++ b/bot/decorators.py @@ -238,9 +238,9 @@ async def wrapper(*args, **kwargs) -> t.Any: if target.top_role >= actor.top_role: log.info( - f"{actor} ({actor.id}) attempted to {cmd} " - f"{target} ({target.id}), who has an equal or higher top role." - ) + "%s (%s) attempted to %s " + "%s (%s), who has an equal or higher top role.", + actor, actor.id, cmd, target, target.id) await ctx.send( f":x: {actor.mention}, you may not {cmd} " "someone with an equal or higher top role." @@ -266,7 +266,7 @@ def decorator(func: t.Callable) -> t.Callable: async def wrapped(*args, **kwargs) -> t.Any: """Short-circuit and log if in debug mode.""" if DEBUG_MODE: - log.debug(f"Function {func.__name__} called with args: {args}, kwargs: {kwargs}") + log.debug("Function %s called with args: %s, kwargs: %s", func.__name__, args, kwargs) return return_value return await func(*args, **kwargs) return wrapped diff --git a/bot/exts/backend/branding/_cog.py b/bot/exts/backend/branding/_cog.py index 154a94f195..2231b14cf1 100644 --- a/bot/exts/backend/branding/_cog.py +++ b/bot/exts/backend/branding/_cog.py @@ -144,12 +144,12 @@ async def apply_asset(self, asset_type: AssetType, download_url: str) -> bool: Return a boolean indicating whether the application was successful. """ - log.info(f"Applying '{asset_type.value}' asset to the guild.") + log.info("Applying '%s' asset to the guild.", asset_type.value) try: file = await self.repository.fetch_file(download_url) except Exception: - log.exception(f"Failed to fetch '{asset_type.value}' asset.") + log.exception("Failed to fetch '%s' asset.", asset_type.value) return False await self.bot.wait_until_guild_available() @@ -163,7 +163,7 @@ async def apply_asset(self, asset_type: AssetType, download_url: str) -> bool: log.exception("Asset upload to Discord failed.") return False except TimeoutError: - log.error(f"Asset upload to Discord timed out after {timeout} seconds.") + log.error("Asset upload to Discord timed out after %s seconds.", timeout) return False else: log.trace("Asset uploaded successfully.") @@ -182,17 +182,17 @@ async def rotate_assets(self, asset_type: AssetType) -> bool: Return a boolean indicating whether a new asset was applied successfully. """ - log.debug(f"Rotating {asset_type.value}s.") + log.debug("Rotating %ss.", asset_type.value) state = await self.asset_caches[asset_type].to_dict() log.trace(f"Total {asset_type.value}s in rotation: {len(state)}.") if not state: # This would only happen if rotation not initiated, but we can handle gracefully. - log.warning(f"Attempted {asset_type.value} rotation with an empty cache. This indicates wrong logic.") + log.warning("Attempted %s rotation with an empty cache. This indicates wrong logic.", asset_type.value) return False if len(state) == 1 and 1 in state.values(): - log.debug(f"Aborting {asset_type.value} rotation: only 1 asset is available and has already been applied.") + log.debug("Aborting %s rotation: only 1 asset is available and has already been applied.", asset_type.value) return False current_iteration = min(state.values()) # Choose iteration to draw from. @@ -219,7 +219,7 @@ async def maybe_rotate_assets(self, asset_type: AssetType) -> None: is work to be done before the timestamp is read and written, the next read will likely commence slightly under 24 hours after the last write. """ - log.debug(f"Checking whether it's time for {asset_type.value}s to rotate.") + log.debug("Checking whether it's time for %ss to rotate.", asset_type.value) last_rotation_timestamp = await self.cache_information.get(f"last_{asset_type.value}_rotation_timestamp") @@ -245,7 +245,7 @@ async def initiate_rotation(self, asset_type: AssetType, available_assets: list[ This function does not upload a new asset! """ - log.debug(f"Initiating new {asset_type.value} rotation.") + log.debug("Initiating new %s rotation.", asset_type.value) await self.asset_caches[asset_type].clear() @@ -265,13 +265,17 @@ async def send_info_embed(self, channel_id: int, *, is_notification: bool) -> No We read event information from `cache_information`. The caller is therefore responsible for making sure that the cache is up-to-date before calling this function. """ - log.debug(f"Sending event information event to channel: {channel_id} ({is_notification=}).") + log.debug( + "Sending event information event to channel: %s (is_notification=%r).", + channel_id, + is_notification, + ) await self.bot.wait_until_guild_available() channel: discord.TextChannel | None = self.bot.get_channel(channel_id) if channel is None: - log.warning(f"Cannot send event information: channel {channel_id} not found!") + log.warning("Cannot send event information: channel %s not found!", channel_id) return log.trace(f"Destination channel: #{channel.name}.") @@ -304,7 +308,7 @@ async def enter_event(self, event: Event) -> tuple[bool, bool]: Return a 2-tuple indicating whether the banner, and the icon, were applied successfully. """ - log.info(f"Entering event: '{event.path}'.") + log.info("Entering event: '%s'.", event.path) # Prepare and apply new icon and banner rotations await self.initiate_rotation(AssetType.ICON, event.icons) @@ -571,7 +575,7 @@ async def branding_calendar_group(self, ctx: commands.Context) -> None: first_25 = list(available_events.items())[:25] if len(first_25) != len(available_events): # Alert core devs that a paginating solution is now necessary. - log.warning(f"There are {len(available_events)} events, but the calendar view can only display 25.") + log.warning("There are %s events, but the calendar view can only display 25.", len(available_events)) for name, duration in first_25: embed.add_field(name=name[:256], value=duration[:1024]) diff --git a/bot/exts/backend/branding/_repository.py b/bot/exts/backend/branding/_repository.py index 18720e581a..55f894ec2e 100644 --- a/bot/exts/backend/branding/_repository.py +++ b/bot/exts/backend/branding/_repository.py @@ -136,7 +136,7 @@ async def fetch_directory(self, path: str, types: t.Container[str] = ("file", "d Passing custom `types` allows getting only files or directories. By default, both are included. """ full_url = f"{BRANDING_URL}/{path}" - log.debug(f"Fetching directory from branding repository: '{full_url}'.") + log.debug("Fetching directory from branding repository: '%s'.", full_url) async with self.bot.http_session.get(full_url, params=PARAMS, headers=HEADERS) as response: _raise_for_status(response) @@ -151,7 +151,7 @@ async def fetch_file(self, download_url: str) -> bytes: Raise an exception if the request does not succeed. """ - log.debug(f"Fetching file from branding repository: '{download_url}'.") + log.debug("Fetching file from branding repository: '%s'.", download_url) async with self.bot.http_session.get(download_url, params=PARAMS, headers=HEADERS) as response: _raise_for_status(response) @@ -245,7 +245,7 @@ async def get_current_event(self) -> tuple[Event, list[Event]]: Events are validated in the branding repo. The bot assumes that events are valid. """ utc_now = datetime.now(tz=UTC) - log.debug(f"Finding active event for: {utc_now}.") + log.debug("Finding active event for: %s.", utc_now) # Construct an object in the arbitrary year for the purpose of comparison. lookup_now = date(year=ARBITRARY_YEAR, month=utc_now.month, day=utc_now.day) diff --git a/bot/exts/backend/config_verifier.py b/bot/exts/backend/config_verifier.py index 84ae5ca92f..2d0fc81cc7 100644 --- a/bot/exts/backend/config_verifier.py +++ b/bot/exts/backend/config_verifier.py @@ -29,7 +29,7 @@ async def cog_load(self) -> None: ] if invalid_channels: - log.warning(f"Configured channels do not exist in server: {invalid_channels}.") + log.warning("Configured channels do not exist in server: %s.", invalid_channels) async def setup(bot: Bot) -> None: diff --git a/bot/exts/backend/error_handler.py b/bot/exts/backend/error_handler.py index 352be313dc..2b63ad65bd 100644 --- a/bot/exts/backend/error_handler.py +++ b/bot/exts/backend/error_handler.py @@ -370,7 +370,7 @@ async def handle_check_failure(ctx: Context, e: errors.CheckFailure) -> None: async def handle_api_error(ctx: Context, e: ResponseCodeError) -> None: """Send an error message in `ctx` for ResponseCodeError and log it.""" if e.status == 404: - log.debug(f"API responded with 404 for command {ctx.command}") + log.debug("API responded with 404 for command %s", ctx.command) await ctx.send("There does not seem to be anything matching your query.") ctx.bot.stats.incr("errors.api_error_404") elif e.status == 400: @@ -382,11 +382,11 @@ async def handle_api_error(ctx: Context, e: ResponseCodeError) -> None: await ctx.send("According to the API, your request is malformed.") ctx.bot.stats.incr("errors.api_error_400") elif 500 <= e.status < 600: - log.warning(f"API responded with {e.status} for command {ctx.command}") + log.warning("API responded with %s for command %s", e.status, ctx.command) await ctx.send("Sorry, there seems to be an internal issue with the API.") ctx.bot.stats.incr("errors.api_internal_server_error") else: - log.warning(f"Unexpected API response for command {ctx.command}: {e.status}") + log.warning("Unexpected API response for command %s: %s", ctx.command, e.status) await ctx.send(f"Got an unexpected status code from the API (`{e.status}`).") ctx.bot.stats.incr(f"errors.api_error_{e.status}") @@ -418,7 +418,7 @@ async def handle_unexpected_error(ctx: Context, e: errors.CommandError) -> None: f"https://discordapp.com/channels/{ctx.guild.id}/{ctx.channel.id}/{ctx.message.id}" ) - log.error(f"Error executing command invoked by {ctx.message.author}: {ctx.message.content}", exc_info=e) + log.error("Error executing command invoked by %s: %s", ctx.message.author, ctx.message.content, exc_info=e) async def setup(bot: Bot) -> None: diff --git a/bot/exts/backend/sync/_syncers.py b/bot/exts/backend/sync/_syncers.py index 9a5671deb6..874de29de8 100644 --- a/bot/exts/backend/sync/_syncers.py +++ b/bot/exts/backend/sync/_syncers.py @@ -52,7 +52,7 @@ async def sync(cls, guild: Guild, ctx: Context | None = None) -> None: If `ctx` is given, send a message with the results. """ - log.info(f"Starting {cls.name} syncer.") + log.info("Starting %s syncer.", cls.name) if ctx: message = await ctx.send(f"📊 Synchronising {cls.name}s.") @@ -63,7 +63,7 @@ async def sync(cls, guild: Guild, ctx: Context | None = None) -> None: try: await cls._sync(diff) except ResponseCodeError as e: - log.exception(f"{cls.name} syncer failed!") + log.exception("%s syncer failed!", cls.name) # Don't show response text because it's probably some really long HTML. results = f"status {e.status}\n```{e.response_json or 'See log output for details'}```" @@ -73,7 +73,7 @@ async def sync(cls, guild: Guild, ctx: Context | None = None) -> None: results = (f"{name} `{len(val)}`" for name, val in diff_dict.items() if val is not None) results = ", ".join(results) - log.info(f"{cls.name} syncer finished: {results}.") + log.info("%s syncer finished: %s.", cls.name, results) content = f":ok_hand: Synchronisation of {cls.name}s complete: {results}" if message: diff --git a/bot/exts/filtering/_filter_lists/antispam.py b/bot/exts/filtering/_filter_lists/antispam.py index ecb895e013..47396c6797 100644 --- a/bot/exts/filtering/_filter_lists/antispam.py +++ b/bot/exts/filtering/_filter_lists/antispam.py @@ -50,7 +50,7 @@ def get_filter_type(self, content: str) -> type[UniqueFilter] | None: return antispam_filter_types[content] except KeyError: if content not in self._already_warned: - log.warning(f"An antispam filter named {content} was supplied, but no matching implementation found.") + log.warning("An antispam filter named %s was supplied, but no matching implementation found.", content) self._already_warned.add(content) return None @@ -124,7 +124,7 @@ async def process_deletion_context() -> None: await asyncio.sleep(ALERT_DELAY) if member not in self.message_deletion_queue: - log.error(f"Started processing deletion queue for context `{member}`, but it was not found!") + log.error("Started processing deletion queue for context `%s`, but it was not found!", member) return deletion_context = self.message_deletion_queue.pop(member) diff --git a/bot/exts/filtering/_filter_lists/filter_list.py b/bot/exts/filtering/_filter_lists/filter_list.py index 9c47a03c1b..fa9e4096ad 100644 --- a/bot/exts/filtering/_filter_lists/filter_list.py +++ b/bot/exts/filtering/_filter_lists/filter_list.py @@ -222,7 +222,7 @@ def _create_filter(self, filter_data: dict, defaults: Defaults) -> T | None: if filter_type: return filter_type(filter_data, defaults) if content not in self._already_warned: - log.warning(f"A filter named {content} was supplied, but no matching implementation found.") + log.warning("A filter named %s was supplied, but no matching implementation found.", content) self._already_warned.add(content) return None except TypeError as e: diff --git a/bot/exts/filtering/_filters/unique/discord_token.py b/bot/exts/filtering/_filters/unique/discord_token.py index 1745fa86cc..1260896457 100644 --- a/bot/exts/filtering/_filters/unique/discord_token.py +++ b/bot/exts/filtering/_filters/unique/discord_token.py @@ -188,7 +188,7 @@ def is_valid_timestamp(b64_content: str) -> bool: decoded_bytes = base64.urlsafe_b64decode(b64_content) timestamp = int.from_bytes(decoded_bytes, byteorder="big") except ValueError as e: - log.debug(f"Failed to decode token timestamp '{b64_content}': {e}") + log.debug("Failed to decode token timestamp '%s': %s", b64_content, e) return False # Seems like newer tokens don't need the epoch added, but add anyway since an upper bound @@ -196,7 +196,7 @@ def is_valid_timestamp(b64_content: str) -> bool: if timestamp + TOKEN_EPOCH >= DISCORD_EPOCH: return True - log.debug(f"Invalid token timestamp '{b64_content}': smaller than Discord epoch") + log.debug("Invalid token timestamp '%s': smaller than Discord epoch", b64_content) return False @staticmethod @@ -210,8 +210,10 @@ def is_maybe_valid_hmac(b64_content: str) -> bool: unique = len(set(b64_content.lower())) if unique <= 3: log.debug( - f"Considering the HMAC {b64_content} a dummy because it has {unique}" - " case-insensitively unique characters" + "Considering the HMAC %s a dummy because it has %s" + " case-insensitively unique characters", + b64_content, + unique, ) return False return True diff --git a/bot/exts/filtering/_settings.py b/bot/exts/filtering/_settings.py index 7bf308f9f2..c45e5fbf37 100644 --- a/bot/exts/filtering/_settings.py +++ b/bot/exts/filtering/_settings.py @@ -39,7 +39,8 @@ def create_settings( validation_data[entry_name] = entry_data elif entry_name not in _already_warned: log.warning( - f"A setting named {entry_name} was loaded from the database, but no matching class." + "A setting named %s was loaded from the database, but no matching class.", + entry_name, ) _already_warned.add(entry_name) if defaults is None: @@ -80,9 +81,9 @@ def __init__(self, settings_data: dict, *, defaults: Settings | None = None, kee except KeyError: if entry_name not in self._already_warned: log.warning( - f"A setting named {entry_name} was loaded from the database, " - f"but no matching {self.entry_type.__name__} class." - ) + "A setting named %s was loaded from the database, " + "but no matching %s class.", + entry_name, self.entry_type.__name__) self._already_warned.add(entry_name) else: try: diff --git a/bot/exts/filtering/_settings_types/actions/infraction_and_notification.py b/bot/exts/filtering/_settings_types/actions/infraction_and_notification.py index 163df16ab2..fe066e85bd 100644 --- a/bot/exts/filtering/_settings_types/actions/infraction_and_notification.py +++ b/bot/exts/filtering/_settings_types/actions/infraction_and_notification.py @@ -94,7 +94,7 @@ async def invoke( command = bot_module.instance.get_command(command_name) if not command: await alerts_channel.send(f":warning: Could not apply {command_name} to {user.mention}: command not found.") - log.warning(f":warning: Could not apply {command_name} to {user.mention}: command not found.") + log.warning(":warning: Could not apply %s to %s: command not found.", command_name, user.mention) return None if isinstance(user, discord.User): # For example because a message was sent in a DM. @@ -103,8 +103,10 @@ async def invoke( user = member else: log.warning( - f"The user {user} were set to receive an automatic {command_name}, " - "but they were not found in the guild." + "The user %s were set to receive an automatic %s, " + "but they were not found in the guild.", + user, + command_name, ) return None @@ -200,14 +202,14 @@ async def action(self, ctx: FilterContext) -> None: if self.infraction_channel: channel = bot_module.instance.get_channel(self.infraction_channel) if not channel: - log.info(f"Could not find a channel with ID {self.infraction_channel}, infracting in mod-alerts.") + log.info("Could not find a channel with ID %s, infracting in mod-alerts.", self.infraction_channel) channel = alerts_channel elif not ctx.channel: channel = alerts_channel else: channel = ctx.channel if not channel: # If somehow it's set to `alerts_channel` and it can't be found. - log.error(f"Unable to apply infraction as the context channel {channel} can't be found.") + log.error("Unable to apply infraction as the context channel %s can't be found.", channel) return infraction_action = await self.infraction_type.invoke( diff --git a/bot/exts/filtering/_settings_types/actions/remove_context.py b/bot/exts/filtering/_settings_types/actions/remove_context.py index b833978fa8..a30f8da3c8 100644 --- a/bot/exts/filtering/_settings_types/actions/remove_context.py +++ b/bot/exts/filtering/_settings_types/actions/remove_context.py @@ -96,13 +96,13 @@ async def _handle_nickname(ctx: FilterContext) -> None: """Apply a superstar infraction to remove the user's nickname.""" alerts_channel = bot.instance.get_channel(Channels.mod_alerts) if not alerts_channel: - log.error(f"Unable to apply superstar as the context channel {alerts_channel} can't be found.") + log.error("Unable to apply superstar as the context channel %s can't be found.", alerts_channel) return command = bot.instance.get_command("superstar") if not command: user = ctx.author await alerts_channel.send(f":warning: Could not apply superstar to {user.mention}: command not found.") - log.warning(f":warning: Could not apply superstar to {user.mention}: command not found.") + log.warning(":warning: Could not apply superstar to %s: command not found.", user.mention) ctx.action_descriptions.append("failed to superstar") return diff --git a/bot/exts/filtering/_ui/ui.py b/bot/exts/filtering/_ui/ui.py index bc8d24b976..9db34fbc17 100644 --- a/bot/exts/filtering/_ui/ui.py +++ b/bot/exts/filtering/_ui/ui.py @@ -108,7 +108,7 @@ async def build_mod_alert(ctx: FilterContext, triggered_filters: dict[FilterList actions = "\n**Actions Taken:** " + (", ".join(ctx.action_descriptions) if ctx.action_descriptions else "-") mod_alert_message = "\n".join(part for part in (triggered_by, triggered_in, filters, matches, actions) if part) - log.debug(f"{ctx.event.name} Filter:\n{mod_alert_message}") + log.debug("%s Filter:\n%s", ctx.event.name, mod_alert_message) if ctx.message: mod_alert_message += f"\n**[Original Content]({ctx.message.jump_url})**:\n" diff --git a/bot/exts/filtering/filtering.py b/bot/exts/filtering/filtering.py index b7c7b6d6d0..0a7795f87d 100644 --- a/bot/exts/filtering/filtering.py +++ b/bot/exts/filtering/filtering.py @@ -619,7 +619,7 @@ async def f_delete(self, ctx: Context, filter_id: int) -> None: async def delete_list() -> None: """The actual removal routine.""" await bot.instance.api_client.delete(f"bot/filter/filters/{filter_id}") - log.info(f"Successfully deleted filter with ID {filter_id}.") + log.info("Successfully deleted filter with ID %s.", filter_id) filter_list[list_type].filters.pop(filter_id) await ctx.reply(f"✅ Deleted filter: {filter_}") @@ -967,7 +967,7 @@ async def delete_list() -> None: self.unsubscribe(filter_list) await bot.instance.api_client.delete(f"bot/filter/filter_lists/{list_id}") - log.info(f"Successfully deleted the {filter_list[list_type].label} filterlist.") + log.info("Successfully deleted the %s filterlist.", filter_list[list_type].label) await message.edit(content=f"✅ The {list_description} list has been deleted.") result = await self._resolve_list_type_and_name(ctx, list_type, list_name) @@ -999,8 +999,8 @@ def _load_raw_filter_list(self, list_data: dict) -> AtomicList | None: if list_name not in filter_list_types: if list_name not in self.already_warned: log.warning( - f"A filter list named {list_name} was loaded from the database, but no matching class." - ) + "A filter list named %s was loaded from the database, but no matching class.", + list_name) self.already_warned.add(list_name) return None self.filter_lists[list_name] = filter_list_types[list_name](self) @@ -1022,7 +1022,7 @@ async def _fetch_or_generate_filtering_webhook(self) -> discord.Webhook | None: log.debug("Successfully fetched filtering webhook icon, reading payload.") webhook_icon = await response.read() else: - log.warning(f"Failed to fetch filtering webhook icon due to status: {response.status}") + log.warning("Failed to fetch filtering webhook icon due to status: %s", response.status) # Generate a new webhook. try: @@ -1030,7 +1030,7 @@ async def _fetch_or_generate_filtering_webhook(self) -> discord.Webhook | None: log.trace(f"Generated new filters webhook with ID {webhook.id},") return webhook except HTTPException as e: - log.error(f"Failed to create filters webhook: {e}") + log.error("Failed to create filters webhook: %s", e) return None async def _resolve_action( @@ -1352,7 +1352,7 @@ async def _post_new_filter( } response = await bot.instance.api_client.post("bot/filter/filters", json=to_serializable(payload)) new_filter = filter_list.add_filter(list_type, response) - log.info(f"Added new filter: {new_filter}.") + log.info("Added new filter: %s.", new_filter) if new_filter: await self._maybe_alert_auto_infraction(filter_list, list_type, new_filter) extra_msg = Filtering._identical_filters_message(content, filter_list, list_type, new_filter) @@ -1404,7 +1404,7 @@ async def _patch_filter( ) # Return type can be None, but if it's being edited then it's not supposed to be. edited_filter = filter_list.add_filter(list_type, response) - log.info(f"Successfully patched filter {edited_filter}.") + log.info("Successfully patched filter %s.", edited_filter) await self._maybe_alert_auto_infraction(filter_list, list_type, edited_filter, filter_) extra_msg = Filtering._identical_filters_message(content, filter_list, list_type, edited_filter) await msg.reply(f"✅ Edited filter: {edited_filter}" + extra_msg) @@ -1414,7 +1414,7 @@ async def _post_filter_list(self, msg: Message, list_name: str, list_type: ListT payload = {"name": list_name, "list_type": list_type.value, **to_serializable(settings)} filterlist_name = f"{past_tense(list_type.name.lower())} {list_name}" response = await bot.instance.api_client.post("bot/filter/filter_lists", json=payload) - log.info(f"Successfully posted the new {filterlist_name} filterlist.") + log.info("Successfully posted the new %s filterlist.", filterlist_name) self._load_raw_filter_list(response) await msg.reply(f"✅ Added a new filter list: {filterlist_name}") @@ -1425,7 +1425,7 @@ async def _patch_filter_list(msg: Message, filter_list: FilterList, list_type: L response = await bot.instance.api_client.patch( f"bot/filter/filter_lists/{list_id}", json=to_serializable(settings) ) - log.info(f"Successfully patched the {filter_list[list_type].label} filterlist, reloading...") + log.info("Successfully patched the %s filterlist, reloading...", filter_list[list_type].label) filter_list.pop(list_type, None) filter_list.add_list(response) await msg.reply(f"✅ Edited filter list: {filter_list[list_type].label}") @@ -1504,14 +1504,14 @@ async def _delete_offensive_msg(self, msg: Mapping[str, int]) -> None: await msg_obj.delete() except discord.NotFound: log.info( - f"Tried to delete message {msg['id']}, but the message can't be found " - f"(it has been probably already deleted)." - ) + "Tried to delete message %s, but the message can't be found " + "(it has been probably already deleted).", + msg["id"]) except HTTPException as e: - log.warning(f"Failed to delete message {msg['id']}: status {e.status}") + log.warning("Failed to delete message %s: status %s", msg["id"], e.status) await self.bot.api_client.delete(f'bot/offensive-messages/{msg["id"]}') - log.info(f"Deleted the offensive message with id {msg['id']}.") + log.info("Deleted the offensive message with id %s.", msg["id"]) def _schedule_msg_delete(self, msg: dict) -> None: """Delete an offensive message once its deletion date is reached.""" @@ -1535,9 +1535,9 @@ async def _maybe_schedule_msg_delete(self, ctx: FilterContext, actions: ActionSe await self.bot.api_client.post("bot/offensive-messages", json=data) except ResponseCodeError as e: if e.status == 400 and "already exists" in e.response_json.get("id", [""])[0]: - log.debug(f"Offensive message {msg.id} already exists.") + log.debug("Offensive message %s already exists.", msg.id) else: - log.error(f"Offensive message {msg.id} failed to post: {e}") + log.error("Offensive message %s failed to post: %s", msg.id, e) else: self._schedule_msg_delete(data) log.trace(f"Offensive message {msg.id} will be deleted on {delete_date}") @@ -1569,7 +1569,7 @@ async def send_weekly_auto_infraction_report( channel = self.bot.get_channel(Channels.mod_meta) elif not is_mod_channel(channel): # Silently fail if output is going to be a non-mod channel. - log.info(f"Auto-infraction report: the channel {channel} is not a mod channel.") + log.info("Auto-infraction report: the channel %s is not a mod channel.", channel) return found_filters = defaultdict(list) diff --git a/bot/exts/fun/off_topic_names.py b/bot/exts/fun/off_topic_names.py index ba7cc2914d..21061aa3f4 100644 --- a/bot/exts/fun/off_topic_names.py +++ b/bot/exts/fun/off_topic_names.py @@ -56,7 +56,7 @@ async def update_names(self) -> None: "bot/off-topic-channel-names", params={"random_items": 3} ) except ResponseCodeError as e: - log.error(f"Failed to get new off-topic channel names: code {e.response.status}") + log.error("Failed to get new off-topic channel names: code %s", e.response.status) raise channel_0, channel_1, channel_2 = (self.bot.get_channel(channel_id) for channel_id in CHANNELS) @@ -75,8 +75,10 @@ async def update_names(self) -> None: return log.debug( - "Updated off-topic channel names to" - f" {channel_0_name}, {channel_1_name} and {channel_2_name}" + "Updated off-topic channel names to %s, %s and %s", + channel_0_name, + channel_1_name, + channel_2_name, ) async def toggle_ot_name_activity(self, ctx: Context, name: str, active: bool) -> None: @@ -121,8 +123,8 @@ async def add_command(self, ctx: Context, *, name: OffTopicName) -> None: if close_match: match = close_match[0] log.info( - f"{ctx.author} tried to add channel name '{name}' but it was too similar to '{match}'" - ) + "%s tried to add channel name '%s' but it was too similar to '%s'", + ctx.author, name, match) await ctx.send( f":x: The channel name `{name}` is too similar to `{match}`, and thus was not added. " f"Use `{BotConfig.prefix}otn forceadd` to override this check." @@ -140,7 +142,7 @@ async def _add_name(self, ctx: Context, name: str) -> None: """Adds an off-topic channel name to the site storage.""" await self.bot.api_client.post("bot/off-topic-channel-names", params={"name": name}) - log.info(f"{ctx.author} added the off-topic channel name '{name}'") + log.info("%s added the off-topic channel name '%s'", ctx.author, name) await ctx.send(f":ok_hand: Added `{name}` to the names list.") @otname_group.command(name="delete", aliases=("remove", "rm", "del", "d")) @@ -149,7 +151,7 @@ async def delete_command(self, ctx: Context, *, name: OffTopicName) -> None: """Removes a off-topic name from the rotation.""" await self.bot.api_client.delete(f"bot/off-topic-channel-names/{name}") - log.info(f"{ctx.author} deleted the off-topic channel name '{name}'") + log.info("%s deleted the off-topic channel name '%s'", ctx.author, name) await ctx.send(f":ok_hand: Removed `{name}` from the names list.") @otname_group.command(name="activate", aliases=("whitelist",)) @@ -205,9 +207,9 @@ async def rename_channel() -> None: name=OTN_FORMATTER.format(number=old_channel_name[OT_NUMBER_INDEX], name=new_channel_name) ) log.info( - f"{ctx.author} Off-topic channel re-named from `{old_ot_name}` " - f"to `{new_channel_name}`." - ) + "%s Off-topic channel re-named from `%s` " + "to `%s`.", + ctx.author, old_ot_name, new_channel_name) await ctx.message.reply( f":ok_hand: Off-topic channel re-named from `{old_ot_name}` " diff --git a/bot/exts/help_channels/_channel.py b/bot/exts/help_channels/_channel.py index dcd0e3c327..8c33f6bebf 100644 --- a/bot/exts/help_channels/_channel.py +++ b/bot/exts/help_channels/_channel.py @@ -111,7 +111,7 @@ async def help_post_opened( bot.instance.stats.incr("help.claimed") if not isinstance(opened_post.owner, discord.Member): - log.debug(f"{opened_post.owner_id} isn't a member. Closing post.") + log.debug("%s isn't a member. Closing post.", opened_post.owner_id) await _close_help_post(opened_post, _stats.ClosingReason.CLEANUP, scheduler) return @@ -210,8 +210,8 @@ async def maybe_archive_idle_post(post_id: int, scheduler: scheduling.Scheduler) # Closing time is in the past. # Add 1 second due to POSIX timestamps being lower resolution than datetime objects. log.info( - f"#{post} ({post.id}) is idle past {closing_time} and will be archived. Reason: {closing_reason.value}" - ) + "#%s (%s) is idle past %s and will be archived. Reason: %s", + post, post.id, closing_time, closing_reason.value) await _close_help_post(post, closing_reason, scheduler) return @@ -219,6 +219,6 @@ async def maybe_archive_idle_post(post_id: int, scheduler: scheduling.Scheduler) # Cancel any existing close task scheduler.cancel(post.id) delay = (closing_time - arrow.utcnow()).seconds - log.info(f"#{post} ({post.id}) is still active; scheduling it to be archived after {delay} seconds.") + log.info("#%s (%s) is still active; scheduling it to be archived after %s seconds.", post, post.id, delay) scheduler.schedule_later(delay, post.id, maybe_archive_idle_post(post.id, scheduler)) diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index 5e0b1799fd..83d7826f8a 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -80,7 +80,7 @@ async def close_command(self, ctx: commands.Context) -> None: """ # Don't use a discord.py check because the check needs to fail silently. if await self.close_check(ctx): - log.info(f"Close command invoked by {ctx.author} in #{ctx.channel}.") + log.info("Close command invoked by %s in #%s.", ctx.author, ctx.channel) await _channel.help_post_closed(ctx.channel, self.scheduler) @help_forum_group.command(name="title", root_aliases=("title",)) @@ -154,6 +154,6 @@ async def on_member_remove(self, member: discord.Member) -> None: if thread.archived: continue - log.debug(f"Notifying help thread {thread.id} that owner {member.id} is no longer in the server.") + log.debug("Notifying help thread %s that owner %s is no longer in the server.", thread.id, member.id) with contextlib.suppress(discord.NotFound): await thread.send(":warning: The owner of this post is no longer in the server.") diff --git a/bot/exts/info/code_snippets.py b/bot/exts/info/code_snippets.py index f127d16508..78104c873b 100644 --- a/bot/exts/info/code_snippets.py +++ b/bot/exts/info/code_snippets.py @@ -295,9 +295,9 @@ async def _parse_snippets(self, content: str) -> str: error_message = error.message log.log( logging.DEBUG if error.status == 404 else logging.ERROR, - f"Failed to fetch code snippet from {match[0]!r}: {error.status} " - f"{error_message} for GET {error.request_info.real_url.human_repr()}" - ) + "Failed to fetch code snippet from %r: %s " + "%s for GET %s", + match[0], error.status, error_message, error.request_info.real_url.human_repr()) continue if isinstance(result, list): diff --git a/bot/exts/info/codeblock/_cog.py b/bot/exts/info/codeblock/_cog.py index 4b0936fbcf..bc3c8dde64 100644 --- a/bot/exts/info/codeblock/_cog.py +++ b/bot/exts/info/codeblock/_cog.py @@ -107,7 +107,7 @@ async def send_instructions(self, message: discord.Message, instructions: str) - The embed will be deleted automatically after 5 minutes. """ - log.info(f"Sending code block formatting instructions for message {message.id}.") + log.info("Sending code block formatting instructions for message %s.", message.id) embed = self.create_embed(instructions) bot_message = await message.channel.send(f"Hey {message.author.mention}!", embed=embed) @@ -154,7 +154,7 @@ async def on_message(self, msg: Message) -> None: await self.send_instructions(msg, instructions) if msg.channel.id not in constants.CodeBlock.channel_whitelist: - log.debug(f"Adding #{msg.channel} to the channel cooldowns.") + log.debug("Adding #%s to the channel cooldowns.", msg.channel) self.channel_cooldowns[msg.channel.id] = time.time() @Cog.listener() diff --git a/bot/exts/info/doc/_batch_parser.py b/bot/exts/info/doc/_batch_parser.py index c583d793bb..2ac846e6b8 100644 --- a/bot/exts/info/doc/_batch_parser.py +++ b/bot/exts/info/doc/_batch_parser.py @@ -115,7 +115,7 @@ async def get_markdown(self, doc_item: _cog.DocItem) -> str | None: ) self._queue.extendleft(QueueItem(item, soup) for item in self._page_doc_items[doc_item.url]) - log.debug(f"Added items from {doc_item.url} to the parse queue.") + log.debug("Added items from %s to the parse queue.", doc_item.url) if self._parse_task is None: self._parse_task = scheduling.create_task(self._parse_queue(), name="Queue parse") @@ -153,7 +153,7 @@ async def _parse_queue(self) -> None: self.stale_inventory_notifier.send_warning(item), name="Stale inventory warning" ) except Exception: - log.exception(f"Unexpected error when handling {item}") + log.exception("Unexpected error when handling %s", item) future.set_result(markdown) del self._item_futures[item] await asyncio.sleep(0.1) diff --git a/bot/exts/info/doc/_cog.py b/bot/exts/info/doc/_cog.py index 4546fc14f3..ba9836d2f0 100644 --- a/bot/exts/info/doc/_cog.py +++ b/bot/exts/info/doc/_cog.py @@ -126,7 +126,7 @@ async def update_or_reschedule_inventory( package = await fetch_inventory(inventory_url) except InvalidHeaderError as e: # Do not reschedule if the header is invalid, as the request went through but the contents are invalid. - log.warning(f"Invalid inventory header at {inventory_url}. Reason: {e}") + log.warning("Invalid inventory header at %s. Reason: %s", inventory_url, e) return if not package: @@ -135,7 +135,7 @@ async def update_or_reschedule_inventory( delay = FETCH_RESCHEDULE_DELAY.repeated else: delay = FETCH_RESCHEDULE_DELAY.first - log.info(f"Failed to fetch inventory; attempting again in {delay} minutes.") + log.info("Failed to fetch inventory; attempting again in %s minutes.", delay) self.inventory_scheduler.schedule_later( delay*60, api_package_name, @@ -239,16 +239,16 @@ async def get_symbol_markdown(self, doc_item: DocItem) -> str: markdown = await doc_cache.get(doc_item) if markdown is None: - log.debug(f"Redis cache miss with {doc_item}.") + log.debug("Redis cache miss with %s.", doc_item) try: markdown = await self.item_fetcher.get_markdown(doc_item) except aiohttp.ClientError as e: - log.warning(f"A network error has occurred when requesting parsing of {doc_item}.", exc_info=e) + log.warning("A network error has occurred when requesting parsing of %s.", doc_item, exc_info=e) return "Unable to parse the requested symbol due to a network error." except Exception: - log.exception(f"An unexpected error has occurred when requesting parsing of {doc_item}.") + log.exception("An unexpected error has occurred when requesting parsing of %s.", doc_item) return "Unable to parse the requested symbol due to an error." if markdown is None: @@ -381,14 +381,16 @@ async def set_command( await self.bot.api_client.post("bot/documentation-links", json=body) except ResponseCodeError as err: if err.status == 400 and "already exists" in err.response_json.get("package", [""])[0]: - log.info(f"Ignoring HTTP 400 as package {package_name} has already been added.") + log.info("Ignoring HTTP 400 as package %s has already been added.", package_name) await ctx.send(f"Package {package_name} has already been added.") return raise log.info( - f"User @{ctx.author} ({ctx.author.id}) added a new documentation package:\n" - + "\n".join(f"{key}: {value}" for key, value in body.items()) + "User @%s (%s) added a new documentation package:\n%s", + ctx.author, + ctx.author.id, + "\n".join(f"{key}: {value}" for key, value in body.items()), ) if not base_url: diff --git a/bot/exts/info/doc/_inventory_parser.py b/bot/exts/info/doc/_inventory_parser.py index 33964be99e..ed657eb44c 100644 --- a/bot/exts/info/doc/_inventory_parser.py +++ b/bot/exts/info/doc/_inventory_parser.py @@ -124,21 +124,21 @@ async def fetch_inventory(url: str) -> InventoryDict | None: inventory = await _fetch_inventory(url) except aiohttp.ClientConnectorError: log.warning( - f"Failed to connect to inventory url at {url}; " - f"trying again ({attempt}/{FAILED_REQUEST_ATTEMPTS})." - ) + "Failed to connect to inventory url at %s; " + "trying again (%s/%s).", + url, attempt, FAILED_REQUEST_ATTEMPTS) except aiohttp.ClientError: log.error( - f"Failed to get inventory from {url}; " - f"trying again ({attempt}/{FAILED_REQUEST_ATTEMPTS})." - ) + "Failed to get inventory from %s; " + "trying again (%s/%s).", + url, attempt, FAILED_REQUEST_ATTEMPTS) except InvalidHeaderError: raise except Exception: log.exception( - f"An unexpected error has occurred during fetching of {url}; " - f"trying again ({attempt}/{FAILED_REQUEST_ATTEMPTS})." - ) + "An unexpected error has occurred during fetching of %s; " + "trying again (%s/%s).", + url, attempt, FAILED_REQUEST_ATTEMPTS) else: return inventory diff --git a/bot/exts/info/doc/_redis_cache.py b/bot/exts/info/doc/_redis_cache.py index 95003dab29..4fcb05851e 100644 --- a/bot/exts/info/doc/_redis_cache.py +++ b/bot/exts/info/doc/_redis_cache.py @@ -41,26 +41,26 @@ async def set(self, item: DocItem, value: str) -> None: if set_expire is None: # An expire is only set if the key didn't exist before. ttl = await self.redis_session.client.ttl(redis_key) - log.debug(f"Checked TTL for `{redis_key}`.") + log.debug("Checked TTL for `%s`.", redis_key) if ttl == -1: - log.warning(f"Key `{redis_key}` had no expire set.") + log.warning("Key `%s` had no expire set.", redis_key) if ttl < 0: # not set or didn't exist needs_expire = True else: - log.debug(f"Key `{redis_key}` has a {ttl} TTL.") + log.debug("Key `%s` has a %s TTL.", redis_key, ttl) self._set_expires[redis_key] = time.monotonic() + ttl - .1 # we need this to expire before redis elif time.monotonic() > set_expire: # If we got here the key expired in redis and we can be sure it doesn't exist. needs_expire = True - log.debug(f"Key `{redis_key}` expired in internal key cache.") + log.debug("Key `%s` expired in internal key cache.", redis_key) await self.redis_session.client.hset(redis_key, item.symbol_id, value) if needs_expire: self._set_expires[redis_key] = time.monotonic() + WEEK_SECONDS await self.redis_session.client.expire(redis_key, WEEK_SECONDS) - log.info(f"Set {redis_key} to expire in a week.") + log.info("Set %s to expire in a week.", redis_key) async def get(self, item: DocItem) -> str | None: """Return the Markdown content of the symbol `item` if it exists.""" @@ -75,7 +75,7 @@ async def delete(self, package: str) -> bool: ] if package_keys: await self.redis_session.client.delete(*package_keys) - log.info(f"Deleted keys from redis: {package_keys}.") + log.info("Deleted keys from redis: %s.", package_keys) self._set_expires = { key: expire for key, expire in self._set_expires.items() if not fnmatch.fnmatchcase(key, pattern) } diff --git a/bot/exts/info/pep.py b/bot/exts/info/pep.py index 4655b21ff0..f4f336bf03 100644 --- a/bot/exts/info/pep.py +++ b/bot/exts/info/pep.py @@ -45,8 +45,8 @@ async def refresh_pep_data(self) -> None: async with self.bot.http_session.get(PEP_API_URL) as resp: if resp.status != 200: log.warning( - f"Fetching PEP data from PEP API failed with code {resp.status}" - ) + "Fetching PEP data from PEP API failed with code %s", + resp.status) return listing = await resp.json() diff --git a/bot/exts/info/python_news.py b/bot/exts/info/python_news.py index c786a9d192..fcb70d04d7 100644 --- a/bot/exts/info/python_news.py +++ b/bot/exts/info/python_news.py @@ -107,7 +107,7 @@ async def post_pep_news(self) -> None: # %Z doesn't actually set the tzinfo of the datetime object, manually set this to UTC pep_creation = datetime.strptime(new["published"], "%a, %d %b %Y %X %Z").replace(tzinfo=UTC) except ValueError: - log.warning(f"Wrong datetime format passed in PEP new: {new['published']}") + log.warning("Wrong datetime format passed in PEP new: %s", new["published"]) continue pep_nr = new["title"].split(":")[0].split()[1] if ( @@ -169,7 +169,7 @@ async def post_maillist_news(self) -> None: try: new_date = datetime.strptime(email_information["date"], "%Y-%m-%dT%X%z") except ValueError: - log.warning(f"Invalid datetime from Thread email: {email_information['date']}") + log.warning("Invalid datetime from Thread email: %s", email_information["date"]) continue thread_id = thread_information["thread_id"] diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py index 6e8f089bfc..5c87ef2e1b 100644 --- a/bot/exts/info/subscribe.py +++ b/bot/exts/info/subscribe.py @@ -207,7 +207,7 @@ async def _fetch_or_create_self_assignable_roles_message(self) -> tuple[discord. async for message in roles_channel.history(limit=30): if message.content == self.SELF_ASSIGNABLE_ROLES_MESSAGE: - log.debug(f"Found self assignable roles view message: {message.id}") + log.debug("Found self assignable roles view message: %s", message.id) return message, None log.debug("Self assignable roles view message hasn't been found, creating a new one.") diff --git a/bot/exts/info/tags.py b/bot/exts/info/tags.py index 65b0c5c650..047db1d0d3 100644 --- a/bot/exts/info/tags.py +++ b/bot/exts/info/tags.py @@ -213,7 +213,7 @@ async def get_tag_embed( if tag is not None: if tag.on_cooldown_in(channel): - log.debug(f"Tag {str(tag_identifier)!r} is on cooldown.") + log.debug("Tag %r is on cooldown.", str(tag_identifier)) return COOLDOWN.obj tag.set_cooldown_for(channel) diff --git a/bot/exts/moderation/defcon.py b/bot/exts/moderation/defcon.py index 3196c4a1a8..06afd4fdb3 100644 --- a/bot/exts/moderation/defcon.py +++ b/bot/exts/moderation/defcon.py @@ -103,7 +103,7 @@ async def _sync_settings(self) -> None: self.scheduler.schedule_at(self.expiry, 0, self._remove_threshold()) self._update_notifier() - log.info(f"DEFCON synchronized: {time.humanize_delta(self.threshold) if self.threshold else '-'}") + log.info("DEFCON synchronized: %s", time.humanize_delta(self.threshold) if self.threshold else "-") await self._update_channel_topic() @@ -114,7 +114,7 @@ async def on_member_join(self, member: Member) -> None: now = arrow.utcnow() if now - member.created_at < time.relativedelta_to_timedelta(self.threshold): - log.info(f"Rejecting user {member}: Account is too new") + log.info("Rejecting user %s: Account is too new", member) message_sent = False @@ -122,10 +122,10 @@ async def on_member_join(self, member: Member) -> None: await member.send(REJECTION_MESSAGE.format(user=member.mention)) message_sent = True except Forbidden: - log.debug(f"Cannot send DEFCON rejection DM to {member}: DMs disabled") + log.debug("Cannot send DEFCON rejection DM to %s: DMs disabled", member) except Exception: # Broadly catch exceptions because DM isn't critical, but it's imperative to kick them. - log.exception(f"Error sending DEFCON rejection message to {member}") + log.exception("Error sending DEFCON rejection message to %s", member) await member.kick(reason="DEFCON active, user is too new") self.bot.stats.incr("defcon.leaves") diff --git a/bot/exts/moderation/incidents.py b/bot/exts/moderation/incidents.py index 9c11ad96c3..b5c5bbdc05 100644 --- a/bot/exts/moderation/incidents.py +++ b/bot/exts/moderation/incidents.py @@ -62,11 +62,11 @@ async def download_file(attachment: discord.Attachment) -> discord.File | None: If the download fails, the reason is logged and None will be returned. 404 and 403 errors are only logged at debug level. """ - log.debug(f"Attempting to download attachment: {attachment.filename}") + log.debug("Attempting to download attachment: %s", attachment.filename) try: return await attachment.to_file() except (discord.NotFound, discord.Forbidden) as exc: - log.debug(f"Failed to download attachment: {exc}") + log.debug("Failed to download attachment: %s", exc) except Exception: log.exception("Failed to download attachment") @@ -220,14 +220,14 @@ async def make_message_link_embed(ctx: Context, message_link: str) -> discord.Em description=f"Message {message_link} not found." ) except discord.DiscordException as e: - log.exception(f"Failed to make message link embed for '{message_link}', raised exception: {e}") + log.exception("Failed to make message link embed for '%s', raised exception: %s", message_link, e) else: channel = message.channel if not channel.permissions_for(channel.guild.get_role(Roles.helpers)).view_channel: log.info( - f"Helpers don't have read permissions in #{channel.name}," - f" not sending message link embed for {message_link}" - ) + "Helpers don't have read permissions in #%s," + " not sending message link embed for %s", + channel.name, message_link) return None embed = discord.Embed( @@ -331,7 +331,7 @@ async def fetch_webhook(self) -> None: try: self.incidents_webhook = await self.bot.fetch_webhook(Webhooks.incidents.id) except discord.HTTPException: - log.error(f"Failed to fetch incidents webhook with id `{Webhooks.incidents.id}`.") + log.error("Failed to fetch incidents webhook with id `%s`.", Webhooks.incidents.id) async def crawl_incidents(self) -> None: """ @@ -348,7 +348,7 @@ async def crawl_incidents(self) -> None: await self.bot.wait_until_guild_available() incidents: discord.TextChannel = self.bot.get_channel(Channels.incidents) - log.debug(f"Crawling messages in #incidents: {CRAWL_LIMIT=}, {CRAWL_SLEEP=}") + log.debug("Crawling messages in #incidents: CRAWL_LIMIT=%r, CRAWL_SLEEP=%r", CRAWL_LIMIT, CRAWL_SLEEP) async for message in incidents.history(limit=CRAWL_LIMIT): if not is_incident(message): @@ -385,7 +385,7 @@ async def archive(self, incident: discord.Message, outcome: Signal, actioned_by: not all information was relayed, return False. This signals that the original message is not safe to be deleted, as we will lose some information. """ - log.info(f"Archiving incident: {incident.id} (outcome: {outcome}, actioned by: {actioned_by})") + log.info("Archiving incident: %s (outcome: %s, actioned by: %s)", incident.id, outcome, actioned_by) embed, attachment_file = await make_embed(incident, outcome, actioned_by) try: @@ -397,7 +397,7 @@ async def archive(self, incident: discord.Message, outcome: Signal, actioned_by: file=attachment_file, ) except Exception: - log.exception(f"Failed to archive incident {incident.id} to #incidents-archive") + log.exception("Failed to archive incident %s to #incidents-archive", incident.id) return False else: log.trace("Message archived successfully!") @@ -438,7 +438,7 @@ async def process_event(self, reaction: str, incident: discord.Message, member: """ members_roles: set[int] = {role.id for role in member.roles} if not members_roles & ALLOWED_ROLES: # Intersection is truthy on at least 1 common element - log.debug(f"Removing invalid reaction: user {member} is not permitted to send signals") + log.debug("Removing invalid reaction: user %s is not permitted to send signals", member) try: await incident.remove_reaction(reaction, member) except discord.NotFound: @@ -448,7 +448,7 @@ async def process_event(self, reaction: str, incident: discord.Message, member: try: signal = Signal(reaction) except ValueError: - log.debug(f"Removing invalid reaction: emoji {reaction} is not a valid signal") + log.debug("Removing invalid reaction: emoji %s is not a valid signal", reaction) try: await incident.remove_reaction(reaction, member) except discord.NotFound: @@ -479,7 +479,7 @@ async def process_event(self, reaction: str, incident: discord.Message, member: try: await confirmation_task except TimeoutError: - log.info(f"Did not receive incident deletion confirmation within {timeout} seconds!") + log.info("Did not receive incident deletion confirmation within %s seconds!", timeout) else: log.trace("Deletion was confirmed") @@ -514,7 +514,7 @@ async def resolve_message(self, message_id: int) -> discord.Message | None: except discord.NotFound: log.trace("Message doesn't exist, it was likely already relayed") except Exception: - log.exception(f"Failed to fetch message {message_id}!") + log.exception("Failed to fetch message %s!", message_id) else: log.trace("Message fetched successfully!") return message @@ -647,8 +647,8 @@ async def send_message_link_embeds( ) except discord.DiscordException: log.exception( - f"Failed to send message link embed {message.id} to #incidents." - ) + "Failed to send message link embed %s to #incidents.", + message.id) else: await self.message_link_embeds_cache.set(message.id, webhook_msg.id) log.trace("Message link embeds sent successfully to #incidents!") diff --git a/bot/exts/moderation/infraction/_scheduler.py b/bot/exts/moderation/infraction/_scheduler.py index 2adcdb485e..b5f7cf9788 100644 --- a/bot/exts/moderation/infraction/_scheduler.py +++ b/bot/exts/moderation/infraction/_scheduler.py @@ -121,16 +121,16 @@ async def _delete_infraction_message( await partial_message.delete() log.trace(f"Deleted infraction message {message_id} in channel {channel_id}.") except discord.NotFound: - log.info(f"Channel or message {message_id} not found in channel {channel_id}.") + log.info("Channel or message %s not found in channel %s.", message_id, channel_id) except discord.Forbidden: - log.info(f"Bot lacks permissions to delete message {message_id} in channel {channel_id}.") + log.info("Bot lacks permissions to delete message %s in channel %s.", message_id, channel_id) except discord.HTTPException as e: if e.code == ARCHIVED_THREAD_ERROR: log.info( - f"Cannot delete message {message_id} in channel {channel_id} because the thread is archived." - ) + "Cannot delete message %s in channel %s because the thread is archived.", + message_id, channel_id) else: - log.exception(f"Issue during scheduled deletion of message {message_id} in channel {channel_id}.") + log.exception("Issue during scheduled deletion of message %s in channel %s.", message_id, channel_id) return # Keep the task in Redis on HTTP errors await self.messages_to_tidy.delete(f"{channel_id}:{message_id}") @@ -170,15 +170,15 @@ async def reapply_infraction( # When user joined and then right after this left again before action completed, this can't apply roles if e.code == 10007 or e.status == 404: log.info( - f"Can't reapply {infraction['type']} to user {infraction['user']} because user left the guild." - ) + "Can't reapply %s to user %s because user left the guild.", + infraction["type"], infraction["user"]) else: log.exception( - f"Got unexpected HTTPException (HTTP {e.status}, Discord code {e.code})" - f"when running {infraction['type']} action for {infraction['user']}." - ) + "Got unexpected HTTPException (HTTP %s, Discord code %s)" + "when running %s action for %s.", + e.status, e.code, infraction["type"], infraction["user"]) else: - log.info(f"Re-applied {infraction['type']} to user {infraction['user']} upon rejoining.") + log.info("Re-applied %s to user %s upon rejoining.", infraction["type"], infraction["user"]) async def apply_infraction( self, @@ -285,11 +285,11 @@ async def apply_infraction( log_msg = f"Failed to apply {' '.join(infr_type.split('_'))} infraction #{id_} to {user}" if isinstance(e, discord.Forbidden): - log.warning(f"{log_msg}: bot lacks permissions.") + log.warning("%s: bot lacks permissions.", log_msg) elif e.code == 10007 or e.status == 404: log.info( - f"Can't apply {infraction['type']} to user {infraction['user']} because user left from guild." - ) + "Can't apply %s to user %s because user left from guild.", + infraction["type"], infraction["user"]) else: log.exception(log_msg) failed = True @@ -320,7 +320,7 @@ async def apply_infraction( except ResponseCodeError as e: confirm_msg += " and failed to delete" log_title += " and failed to delete" - log.error(f"Deletion of {infr_type} infraction #{id_} failed with error code {e.status}.") + log.error("Deletion of %s infraction #%s failed with error code %s.", infr_type, id_, e.status) infr_message = "" # Send a confirmation message to the invoking context. @@ -372,7 +372,14 @@ async def apply_infraction( footer=f"ID: {id_}" ) - log.info(f"{'Failed to apply' if failed else 'Applied'} {purge}{infr_type} infraction #{id_} to {user}.") + log.info( + "%s %s%s infraction #%s to %s.", + "Failed to apply" if failed else "Applied", + purge, + infr_type, + id_, + user, + ) return not failed async def pardon_infraction( @@ -410,7 +417,7 @@ async def pardon_infraction( ) if not response: - log.debug(f"No active {infr_type} infraction found for {user}.") + log.debug("No active %s infraction found for %s.", infr_type, user) await ctx.send(f":x: There's no active {infr_type} infraction for user {user.mention}.") return @@ -436,12 +443,12 @@ async def pardon_infraction( log_title = "pardon failed" log_content = ctx.author.mention - log.warning(f"Failed to pardon {infr_type} infraction #{id_} for {user}.") + log.warning("Failed to pardon %s infraction #%s for %s.", infr_type, id_, user) else: confirm_msg = ":ok_hand: pardoned" log_title = "pardoned" - log.info(f"Pardoned {infr_type} infraction #{id_} for {user}.") + log.info("Pardoned %s infraction #%s for %s.", infr_type, id_, user) # Send a confirmation message to the invoking context. if send_msg: @@ -496,7 +503,7 @@ async def deactivate_infraction( type_ = infraction["type"] id_ = infraction["id"] - log.info(f"Marking infraction #{id_} as inactive (expired).") + log.info("Marking infraction #%s as inactive (expired).", id_) log_content = None log_text = { @@ -517,18 +524,18 @@ async def deactivate_infraction( f"Attempted to deactivate an unsupported infraction #{id_} ({type_})!" ) except discord.Forbidden: - log.warning(f"Failed to deactivate infraction #{id_} ({type_}): bot lacks permissions.") + log.warning("Failed to deactivate infraction #%s (%s): bot lacks permissions.", id_, type_) log_text["Failure"] = "The bot lacks permissions to do this (role hierarchy?)" log_content = mod_role.mention except discord.HTTPException as e: if e.code == 10007 or e.status == 404: log.info( - f"Can't pardon {infraction['type']} for user {infraction['user']} because user left the guild." - ) + "Can't pardon %s for user %s because user left the guild.", + infraction["type"], infraction["user"]) log_text["Failure"] = "User left the guild." log_content = mod_role.mention else: - log.exception(f"Failed to deactivate infraction #{id_} ({type_})") + log.exception("Failed to deactivate infraction #%s (%s)", id_, type_) log_text["Failure"] = f"HTTPException with status {e.status} and code {e.code}." log_content = mod_role.mention @@ -547,7 +554,7 @@ async def deactivate_infraction( log_text["Watching"] = "Yes" if active_watch else "No" except ResponseCodeError: - log.exception(f"Failed to fetch watch status for user {user_id}") + log.exception("Failed to fetch watch status for user %s", user_id) log_text["Watching"] = "Unknown - failed to fetch watch status." try: @@ -569,7 +576,7 @@ async def deactivate_infraction( json=data ) except ResponseCodeError as e: - log.exception(f"Failed to deactivate infraction #{id_} ({type_})") + log.exception("Failed to deactivate infraction #%s (%s)", id_, type_) log_line = f"API request failed with code {e.status}." log_content = mod_role.mention diff --git a/bot/exts/moderation/infraction/_utils.py b/bot/exts/moderation/infraction/_utils.py index 752f3ad38a..79a4e29efa 100644 --- a/bot/exts/moderation/infraction/_utils.py +++ b/bot/exts/moderation/infraction/_utils.py @@ -90,10 +90,10 @@ async def post_user(ctx: Context, user: MemberOrUser) -> dict | None: try: response = await ctx.bot.api_client.post("bot/users", json=payload) - log.info(f"User {user.id} added to the DB.") + log.info("User %s added to the DB.", user.id) return response except ResponseCodeError as e: - log.error(f"Failed to add user {user.id} to the DB. {e}") + log.error("Failed to add user %s to the DB. %s", user.id, e) await ctx.send(f":x: The attempt to add the user to the DB failed: status {e.status}") @@ -152,7 +152,7 @@ async def post_infraction( if not should_post_user or await post_user(ctx, user) is None: return None else: - log.exception(f"Unexpected error while adding an infraction for {user}:") + log.exception("Unexpected error while adding an infraction for %s:", user) await ctx.send(f":x: There was an error adding the infraction: status {e.status}.") return None return None @@ -271,7 +271,7 @@ async def notify_infraction( f"bot/infractions/{infr_id}", json={"dm_sent": True} ) - log.debug(f"Update infraction #{infr_id} dm_sent field to true.") + log.debug("Update infraction #%s dm_sent field to true.", infr_id) return dm_sent @@ -306,9 +306,9 @@ async def send_private_embed(user: MemberOrUser, embed: discord.Embed) -> bool: return True except (discord.HTTPException, discord.Forbidden, discord.NotFound): log.debug( - f"Infraction-related information could not be sent to user {user} ({user.id}). " - "The user either could not be retrieved or probably disabled their DMs." - ) + "Infraction-related information could not be sent to user %s (%s). " + "The user either could not be retrieved or probably disabled their DMs.", + user, user.id) return False diff --git a/bot/exts/moderation/infraction/infractions.py b/bot/exts/moderation/infraction/infractions.py index 7ce574a074..0b955b5359 100644 --- a/bot/exts/moderation/infraction/infractions.py +++ b/bot/exts/moderation/infraction/infractions.py @@ -503,7 +503,7 @@ async def action() -> None: if infraction.get("expires_at") is not None: log.trace(f"Ban isn't permanent; user {user} won't be unwatched by Big Brother.") elif not bb_cog: - log.error(f"Big Brother cog not loaded; perma-banned user {user} won't be unwatched.") + log.error("Big Brother cog not loaded; perma-banned user %s won't be unwatched.", user) else: log.trace(f"Big Brother cog loaded; attempting to unwatch perma-banned user {user}.") bb_reason = "User has been permanently banned from the server. Automatically removed." @@ -570,7 +570,7 @@ async def pardon_timeout( log_text["Member"] = format_user(user) else: - log.info(f"Failed to remove timeout from user {user_id}: user not found") + log.info("Failed to remove timeout from user %s: user not found", user_id) log_text["Failure"] = "User was not found in the guild." return log_text @@ -585,7 +585,7 @@ async def pardon_ban(self, user_id: int, guild: discord.Guild, reason: str | Non try: await guild.unban(user, reason=reason) except discord.NotFound: - log.info(f"Failed to unban user {user_id}: no active ban found on Discord") + log.info("Failed to unban user %s: no active ban found on Discord", user_id) log_text["Note"] = "No active ban found on Discord." return log_text diff --git a/bot/exts/moderation/infraction/superstarify.py b/bot/exts/moderation/infraction/superstarify.py index 006334755d..7091c3d412 100644 --- a/bot/exts/moderation/infraction/superstarify.py +++ b/bot/exts/moderation/infraction/superstarify.py @@ -70,9 +70,9 @@ async def on_member_update(self, before: Member, after: Member) -> None: ) log.info( - f"{after.display_name} ({after.id}) tried to escape superstar prison. " - f"Changing the nick back to {before.display_name}." - ) + "%s (%s) tried to escape superstar prison. " + "Changing the nick back to %s.", + after.display_name, after.id, before.display_name) await after.edit( nick=forced_nick, reason=f"Superstarified member tried to escape the prison: {infr_id}" @@ -152,7 +152,7 @@ async def superstarify( # Apply the infraction async def action() -> None: - log.debug(f"Changing nickname of {member} to {forced_nick}.") + log.debug("Changing nickname of %s to %s.", member, forced_nick) self.mod_log.ignore(constants.Event.member_update, member.id) await member.edit(nick=forced_nick, reason=reason) @@ -206,8 +206,8 @@ async def _pardon_action(self, infraction: _utils.Infraction, notify: bool) -> d # Don't bother sending a notification if the user left the guild. if not user: log.debug( - "User left the guild and therefore won't be notified about superstar " - f"{infraction['id']} pardon." + "User left the guild and therefore won't be notified about superstar %s pardon.", + infraction["id"], ) return {} diff --git a/bot/exts/moderation/metabase.py b/bot/exts/moderation/metabase.py index 82c32c8643..e9335a0a37 100644 --- a/bot/exts/moderation/metabase.py +++ b/bot/exts/moderation/metabase.py @@ -46,7 +46,7 @@ async def cog_command_error(self, ctx: Context, error: Exception) -> None: if error.original.status == 403: # User doesn't have access to the given question - log.warning(f"Failed to auth with Metabase for {error.original.url}.") + log.warning("Failed to auth with Metabase for %s.", error.original.url) await ctx.send(f":x: {ctx.author.mention} Failed to auth with Metabase for that question.") elif error.original.status == 404: await ctx.send(f":x: {ctx.author.mention} That question could not be found.") diff --git a/bot/exts/moderation/silence.py b/bot/exts/moderation/silence.py index a299d7eedd..131d8410d7 100644 --- a/bot/exts/moderation/silence.py +++ b/bot/exts/moderation/silence.py @@ -80,9 +80,9 @@ async def _notifier(self) -> None: # Wait for 15 minutes between notices with pause at start of loop. if self._current_loop and not self._current_loop/60 % 15: log.debug( - f"Sending notice with channels: " - f"{', '.join(f'#{channel} ({channel.id})' for channel in self._silenced_channels)}." - ) + "Sending notice with channels: " + "%s.", + ", ".join(f"#{channel} ({channel.id})" for channel in self._silenced_channels)) channels_text = ", ".join( f"{channel.mention} for {(self._current_loop-start)//60} min" for channel, start in self._silenced_channels.items() @@ -176,7 +176,7 @@ async def silence( channel, duration = self.parse_silence_args(ctx, duration_or_channel, duration) channel_info = f"#{channel} ({channel.id})" - log.debug(f"{ctx.author} is silencing channel {channel_info}.") + log.debug("%s is silencing channel %s.", ctx.author, channel_info) # Since threads don't have specific overrides, we cannot silence them individually. # The parent channel has to be muted or the thread should be archived. @@ -185,7 +185,7 @@ async def silence( return if not await self._set_silence_overwrites(channel, kick=kick): - log.info(f"Tried to silence channel {channel_info} but the channel was already silenced.") + log.info("Tried to silence channel %s but the channel was already silenced.", channel_info) await self.send_message(MSG_SILENCE_FAIL, ctx.channel, channel, alert_target=False) return @@ -199,11 +199,11 @@ async def silence( if duration is None: self.notifier.add_channel(channel) - log.info(f"Silenced {channel_info} indefinitely.") + log.info("Silenced %s indefinitely.", channel_info) await self.send_message(MSG_SILENCE_PERMANENT, ctx.channel, channel, alert_target=True) else: - log.info(f"Silenced {channel_info} for {duration} minute(s).") + log.info("Silenced %s for %s minute(s).", channel_info, duration) formatted_message = MSG_SILENCE_SUCCESS.format(duration=duration) await self.send_message(formatted_message, ctx.channel, channel, alert_target=True) @@ -278,7 +278,7 @@ async def unsilence(self, ctx: Context, *, channel: TextOrVoiceChannel = None) - """ if channel is None: channel = ctx.channel - log.debug(f"Unsilencing channel #{channel} from {ctx.author}'s command.") + log.debug("Unsilencing channel #%s from %s's command.", channel, ctx.author) await self._unsilence_wrapper(channel, ctx) @lock_arg(LOCK_NAMESPACE, "channel", raise_error=True) @@ -323,7 +323,7 @@ async def _unsilence(self, channel: TextOrVoiceChannel) -> bool: # Get stored overwrites, and return if channel is unsilenced prev_overwrites = await self.previous_overwrites.get(channel.id) if channel.id not in self.scheduler and prev_overwrites is None: - log.info(f"Tried to unsilence channel #{channel} ({channel.id}) but the channel was not silenced.") + log.info("Tried to unsilence channel #%s (%s) but the channel was not silenced.", channel, channel.id) return False # Select the role based on channel type, and get current overwrites @@ -338,7 +338,7 @@ async def _unsilence(self, channel: TextOrVoiceChannel) -> bool: # Check if old overwrites were not stored if prev_overwrites is None: - log.info(f"Missing previous overwrites for #{channel} ({channel.id}); defaulting to None.") + log.info("Missing previous overwrites for #%s (%s); defaulting to None.", channel, channel.id) overwrite.update( send_messages=None, add_reactions=None, @@ -356,7 +356,7 @@ async def _unsilence(self, channel: TextOrVoiceChannel) -> bool: if isinstance(channel, VoiceChannel): await self._force_voice_sync(channel) - log.info(f"Unsilenced channel #{channel} ({channel.id}).") + log.info("Unsilenced channel #%s (%s).", channel, channel.id) self.scheduler.cancel(channel.id) self.notifier.remove_channel(channel) @@ -383,14 +383,14 @@ async def _get_afk_channel(guild: Guild) -> VoiceChannel: guild.default_role: PermissionOverwrite(speak=False, connect=False, view_channel=False) } afk_channel = await guild.create_voice_channel("mute-temp", overwrites=overwrites) - log.info(f"Failed to get afk-channel, created #{afk_channel} ({afk_channel.id})") + log.info("Failed to get afk-channel, created #%s (%s)", afk_channel, afk_channel.id) return afk_channel @staticmethod async def _kick_voice_members(channel: VoiceChannel) -> None: """Remove all non-staff members from a voice channel.""" - log.debug(f"Removing all non staff members from #{channel.name} ({channel.id}).") + log.debug("Removing all non staff members from #%s (%s).", channel.name, channel.id) for member in channel.members: # Skip staff @@ -401,7 +401,7 @@ async def _kick_voice_members(channel: VoiceChannel) -> None: await member.move_to(None, reason="Kicking member from voice channel.") log.trace(f"Kicked {member.name} from voice channel.") except Exception as e: - log.debug(f"Failed to move {member.name}. Reason: {e}") + log.debug("Failed to move %s. Reason: %s", member.name, e) continue log.debug("Removed all members.") @@ -430,7 +430,7 @@ async def _force_voice_sync(self, channel: VoiceChannel) -> None: await member.move_to(channel, reason="Muting VC member.") log.trace(f"Moved {member.name} to original voice channel.") except Exception as e: - log.debug(f"Failed to move {member.name}. Reason: {e}") + log.debug("Failed to move %s. Reason: %s", member.name, e) continue finally: @@ -443,11 +443,11 @@ async def _reschedule(self) -> None: for channel_id, timestamp in await self.unsilence_timestamps.items(): channel = self.bot.get_channel(channel_id) if channel is None: - log.info(f"Can't reschedule silence for {channel_id}: channel not found.") + log.info("Can't reschedule silence for %s: channel not found.", channel_id) continue if timestamp == -1: - log.info(f"Adding permanent silence for #{channel} ({channel.id}) to the notifier.") + log.info("Adding permanent silence for #%s (%s) to the notifier.", channel, channel.id) self.notifier.add_channel(channel) continue @@ -458,7 +458,7 @@ async def _reschedule(self) -> None: with suppress(LockedResourceError): await self._unsilence_wrapper(channel) else: - log.info(f"Rescheduling silence for #{channel} ({channel.id}).") + log.info("Rescheduling silence for #%s (%s).", channel, channel.id) self.scheduler.schedule_later(delta, channel_id, self._unsilence_wrapper(channel)) # This cannot be static (must have a __func__ attribute). diff --git a/bot/exts/moderation/slowmode.py b/bot/exts/moderation/slowmode.py index a83692fa46..86a1d59e81 100644 --- a/bot/exts/moderation/slowmode.py +++ b/bot/exts/moderation/slowmode.py @@ -90,9 +90,9 @@ async def set_slowmode( # Ensure the delay is within discord's limits if slowmode_delay > SLOWMODE_MAX_DELAY: log.info( - f"{ctx.author} tried to set the slowmode delay of #{channel} to {humanized_delay}, " - "which is not between 0 and 6 hours." - ) + "%s tried to set the slowmode delay of #%s to %s, " + "which is not between 0 and 6 hours.", + ctx.author, channel, humanized_delay) await ctx.send( f"{Emojis.cross_mark} The slowmode delay must be between 0 and 6 hours." @@ -113,9 +113,9 @@ async def set_slowmode( self.scheduler.schedule_at(expiry, channel.id, self._revert_slowmode(channel.id)) log.info( - f"{ctx.author} set the slowmode delay for #{channel} to {humanized_delay}" - f" which will revert to {humanized_original_delay} in {time.humanize_delta(expiry)}." - ) + "%s set the slowmode delay for #%s to %s" + " which will revert to %s in %s.", + ctx.author, channel, humanized_delay, humanized_original_delay, time.humanize_delta(expiry)) await channel.edit(slowmode_delay=slowmode_delay) await ctx.send( f"{Emojis.check_mark} The slowmode delay for {channel.mention}" @@ -126,13 +126,13 @@ async def set_slowmode( await self.slowmode_cache.delete(channel.id) self.scheduler.cancel(channel.id) - log.info(f"{ctx.author} set the slowmode delay for #{channel} to {humanized_delay}.") + log.info("%s set the slowmode delay for #%s to %s.", ctx.author, channel, humanized_delay) await channel.edit(slowmode_delay=slowmode_delay) await ctx.send( f"{Emojis.check_mark} The slowmode delay for {channel.mention} is now {humanized_delay}." ) if channel.id in COMMONLY_SLOWMODED_CHANNELS: - log.info(f"Recording slowmode change in stats for {channel.name}.") + log.info("Recording slowmode change in stats for %s.", channel.name) self.bot.stats.gauge(f"slowmode.{COMMONLY_SLOWMODED_CHANNELS[channel.id]}", slowmode_delay) async def _reschedule(self) -> None: @@ -141,7 +141,7 @@ async def _reschedule(self) -> None: expiration = cached_data.split(", ")[1] expiration_datetime = datetime.fromisoformat(expiration) channel = self.bot.get_channel(channel_id) - log.info(f"Rescheduling slowmode expiration for #{channel} ({channel_id}).") + log.info("Rescheduling slowmode expiration for #%s (%s).", channel, channel_id) self.scheduler.schedule_at(expiration_datetime, channel_id, self._revert_slowmode(channel_id)) async def _fetch_sm_cache(self, channel_id: int) -> tuple[int | None, str, str]: @@ -166,8 +166,8 @@ async def _revert_slowmode(self, channel_id: int) -> None: channel = await get_or_fetch_channel(self.bot, channel_id) mod_channel = await get_or_fetch_channel(self.bot, Channels.mods) log.info( - f"Slowmode in #{channel.name} ({channel.id}) has expired and has reverted to {humanized_original_delay}." - ) + "Slowmode in #%s (%s) has expired and has reverted to %s.", + channel.name, channel.id, humanized_original_delay) await channel.edit(slowmode_delay=original_delay) await mod_channel.send( f"{Emojis.check_mark} A previously applied slowmode in {channel.jump_url} ({channel.id})" diff --git a/bot/exts/moderation/stream.py b/bot/exts/moderation/stream.py index 1efe478200..925b75bf41 100644 --- a/bot/exts/moderation/stream.py +++ b/bot/exts/moderation/stream.py @@ -60,7 +60,7 @@ async def cog_load(self) -> None: continue revoke_time = Arrow.utcfromtimestamp(value) - log.debug(f"Scheduling {member} ({member.id}) to have streaming permission revoked at {revoke_time}") + log.debug("Scheduling %s (%s) to have streaming permission revoked at %s", member, member.id, revoke_time) self.scheduler.schedule_at( revoke_time, key, @@ -84,10 +84,10 @@ async def _suspend_stream(self, ctx: commands.Context, member: discord.Member) - # Notify. await ctx.send(f"{member.mention}'s stream has been suspended!") - log.debug(f"Successfully suspended stream from {member} ({member.id}).") + log.debug("Successfully suspended stream from %s (%s).", member, member.id) return - log.debug(f"No stream found to suspend from {member} ({member.id}).") + log.debug("No stream found to suspend from %s (%s).", member, member.id) @commands.command(aliases=("streaming",)) @commands.has_any_role(*MODERATION_ROLES) @@ -127,7 +127,7 @@ async def stream( already_allowed = any(Roles.video == role.id for role in member.roles) if already_allowed: await ctx.send(f"{Emojis.cross_mark} {member.mention} can already stream.") - log.debug(f"{member} ({member.id}) already has permission to stream.") + log.debug("%s (%s) already has permission to stream.", member, member.id) return # Schedule task to remove streaming permission from Member and add it to task cache @@ -142,9 +142,9 @@ async def stream( humanized_duration = time.humanize_delta(duration, arrow.utcnow(), max_units=2) end_time = duration.strftime("%Y-%m-%d %H:%M:%S") log.debug( - f"Successfully gave {member} ({member.id}) permission " - f"to stream for {humanized_duration} (until {end_time})." - ) + "Successfully gave %s (%s) permission " + "to stream for %s (until %s).", + member, member.id, humanized_duration, end_time) @commands.command(aliases=("pstream",)) @commands.has_any_role(*MODERATION_ROLES) @@ -161,17 +161,17 @@ async def permanentstream(self, ctx: commands.Context, member: discord.Member) - await ctx.send(f"{Emojis.check_mark} Permanently granted {member.mention} the permission to stream.") log.debug( - f"Successfully upgraded temporary streaming permission for {member} ({member.id}) to permanent." - ) + "Successfully upgraded temporary streaming permission for %s (%s) to permanent.", + member, member.id) return await ctx.send(f"{Emojis.cross_mark} This member can already stream.") - log.debug(f"{member} ({member.id}) already had permanent streaming permission.") + log.debug("%s (%s) already had permanent streaming permission.", member, member.id) return await member.add_roles(discord.Object(Roles.video), reason="Permanent streaming access granted") await ctx.send(f"{Emojis.check_mark} Permanently granted {member.mention} the permission to stream.") - log.debug(f"Successfully gave {member} ({member.id}) permanent streaming permission.") + log.debug("Successfully gave %s (%s) permanent streaming permission.", member, member.id) @commands.command(aliases=("unstream", "rstream")) @commands.has_any_role(*MODERATION_ROLES) @@ -188,11 +188,11 @@ async def revokestream(self, ctx: commands.Context, member: discord.Member) -> N await self._revoke_streaming_permission(member) await ctx.send(f"{Emojis.check_mark} Revoked the permission to stream from {member.mention}.") - log.debug(f"Successfully revoked streaming permission from {member} ({member.id}).") + log.debug("Successfully revoked streaming permission from %s (%s).", member, member.id) else: await ctx.send(f"{Emojis.cross_mark} This member doesn't have video permissions to remove!") - log.debug(f"{member} ({member.id}) didn't have the streaming permission to remove!") + log.debug("%s (%s) didn't have the streaming permission to remove!", member, member.id) await self._suspend_stream(ctx, member) diff --git a/bot/exts/moderation/watchchannels/_watchchannel.py b/bot/exts/moderation/watchchannels/_watchchannel.py index 44c0be2a7e..6259f19c26 100644 --- a/bot/exts/moderation/watchchannels/_watchchannel.py +++ b/bot/exts/moderation/watchchannels/_watchchannel.py @@ -96,12 +96,12 @@ async def cog_load(self) -> None: try: self.channel = await get_or_fetch_channel(self.bot, self.destination) except HTTPException: - self.log.exception(f"Failed to retrieve the text channel with id `{self.destination}`") + self.log.exception("Failed to retrieve the text channel with id `%s`", self.destination) try: self.webhook = await self.bot.fetch_webhook(self.webhook_id) except discord.HTTPException: - self.log.exception(f"Failed to fetch webhook with id `{self.webhook_id}`") + self.log.exception("Failed to fetch webhook with id `%s`", self.webhook_id) if self.channel is None or self.webhook is None: self.log.error("Failed to start the watch channel; unloading the cog.") @@ -379,8 +379,8 @@ def done_callback(task: asyncio.Task) -> None: task.result() except asyncio.CancelledError: self.log.info( - f"The consume task of {type(self).__name__} was canceled. Messages may be lost." - ) + "The consume task of %s was canceled. Messages may be lost.", + type(self).__name__) self._consume_task.add_done_callback(done_callback) self._consume_task.cancel() diff --git a/bot/exts/moderation/watchchannels/bigbrother.py b/bot/exts/moderation/watchchannels/bigbrother.py index 7af8c7152b..d9d55e60b2 100644 --- a/bot/exts/moderation/watchchannels/bigbrother.py +++ b/bot/exts/moderation/watchchannels/bigbrother.py @@ -153,7 +153,7 @@ async def apply_unwatch(self, ctx: Context, user: MemberOrUser, reason: str, sen self._remove_user(user.id) if not send_message: # Prevents a message being sent to the channel if part of a permanent ban - log.debug(f"Perma-banned user {user} was unwatched.") + log.debug("Perma-banned user %s was unwatched.", user) return log.trace("User is not banned. Sending message to channel") message = f":white_check_mark: Messages sent by {user.mention} will no longer be relayed." @@ -161,7 +161,7 @@ async def apply_unwatch(self, ctx: Context, user: MemberOrUser, reason: str, sen else: log.trace("No active watches found for user.") if not send_message: # Prevents a message being sent to the channel if part of a permanent ban - log.debug(f"{user} was not on the watch list; no removal necessary.") + log.debug("%s was not on the watch list; no removal necessary.", user) return log.trace("User is not perma banned. Send the error message.") message = ":x: The specified user is currently not being watched." diff --git a/bot/exts/recruitment/talentpool/_cog.py b/bot/exts/recruitment/talentpool/_cog.py index 1e55b2d796..ba7622210b 100644 --- a/bot/exts/recruitment/talentpool/_cog.py +++ b/bot/exts/recruitment/talentpool/_cog.py @@ -67,7 +67,7 @@ async def on_submit(self, interaction: discord.Interaction) -> None: f":x: {self.target.mention} can't be found in the database tables.", ephemeral=True ) - log.warning(f"Could not find {self.target.author} in the site database tables, sync may be broken") + log.warning("Could not find %s in the site database tables, sync may be broken", self.target.author) return raise e @@ -388,7 +388,7 @@ async def maybe_relay_update(self, nominee_id: int, update: str) -> None: nomination = await self.api.get_active_nomination(nominee_id) if nomination and nomination.thread_id: - log.debug(f"Found thread ID for {nominee_id} nomination, relaying new context.") + log.debug("Found thread ID for %s nomination, relaying new context.", nominee_id) thread = await get_or_fetch_channel(self.bot, nomination.thread_id) await thread.send(update) @@ -517,8 +517,10 @@ async def _nominate_context_error( scope.set_extra("interaction_data", interaction.data) log.error( - f"Error executing application command '{interaction.command.name}' invoked by {interaction.user}", - exc_info=error + "Error executing application command '%s' invoked by %s", + interaction.command.name, + interaction.user, + exc_info=error, ) async def _nominate_user(self, ctx: Context, user: MemberOrUser, reason: str) -> None: @@ -858,7 +860,7 @@ async def on_raw_reaction_add(self, payload: RawReactionActionEvent) -> None: emoji = str(payload.emoji) if emoji in {Emojis.incident_actioned, Emojis.incident_unactioned}: - log.info(f"Archiving nomination {message.id}") + log.info("Archiving nomination %s", message.id) await self.reviewer.archive_vote(message, emoji == Emojis.incident_actioned) async def end_nomination(self, user_id: int, reason: str) -> bool: @@ -866,10 +868,10 @@ async def end_nomination(self, user_id: int, reason: str) -> bool: active_nominations = await self.api.get_nominations(user_id, active=True) if not active_nominations: - log.debug(f"No active nomination exists for {user_id=}") + log.debug("No active nomination exists for user_id=%r", user_id) return False - log.info(f"Ending nomination: {user_id=} {reason=}") + log.info("Ending nomination: user_id=%r reason=%r", user_id, reason) nomination = active_nominations[0] await self.api.edit_nomination(nomination.id, end_reason=reason, active=False) diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py index 1535aaa45a..26a74ecaaf 100644 --- a/bot/exts/recruitment/talentpool/_review.py +++ b/bot/exts/recruitment/talentpool/_review.py @@ -235,7 +235,7 @@ async def post_review(self, nomination: Nomination) -> None: guild = self.bot.get_guild(Guild.id) channel = guild.get_channel(Channels.nomination_voting) - log.info(f"Posting the review of {nominee} ({nominee.id})") + log.info("Posting the review of %s (%s)", nominee, nominee.id) vote_message = await channel.send(review) if reviewed_emoji: @@ -325,7 +325,7 @@ async def archive_vote(self, message: PartialMessage, passed: bool) -> None: try: nomination_thread = await message.guild.fetch_channel(message.id) except NotFound: - log.warning(f"Could not find a thread linked to {message.channel.id}-{message.id}") + log.warning("Could not find a thread linked to %s-%s", message.channel.id, message.id) # We assume that the first user mentioned is the user that we are voting on user_id = int(NOMINATION_MESSAGE_REGEX.search(message.content).group(1)) diff --git a/bot/exts/utils/attachment_pastebin_uploader.py b/bot/exts/utils/attachment_pastebin_uploader.py index c788111d99..0dced58c39 100644 --- a/bot/exts/utils/attachment_pastebin_uploader.py +++ b/bot/exts/utils/attachment_pastebin_uploader.py @@ -141,7 +141,7 @@ async def on_message(self, message: discord.Message) -> None: f" contents copied from [your message](<{message.jump_url}>)" ) except discord.Forbidden: - log.debug(f"User {message.author} has DMs disabled, skipping delete link DM.") + log.debug("User %s has DMs disabled, skipping delete link DM.", message.author) # Edit the bot message to contain the link to the paste. await bot_reply.edit(content=f"[Click here]({paste_response.link}) to see this code in our pastebin.") diff --git a/bot/exts/utils/extensions.py b/bot/exts/utils/extensions.py index a687d42b40..8ae9116b73 100644 --- a/bot/exts/utils/extensions.py +++ b/bot/exts/utils/extensions.py @@ -122,7 +122,7 @@ async def list_command(self, ctx: Context) -> None: extensions = "\n".join(sorted(extensions)) lines.append(f"**{category}**\n{extensions}\n") - log.debug(f"{ctx.author} requested a list of all cogs. Returning a paginated list.") + log.debug("%s requested a list of all cogs. Returning a paginated list.", ctx.author) await LinePaginator.paginate(lines, ctx, embed, scale_to_size=700, empty=False) def group_extension_statuses(self) -> t.Mapping[str, str]: @@ -180,7 +180,7 @@ async def batch_manage(self, action: Action, ctx: Context, *extensions: str) -> failures = "\n".join(f"{ext}\n {err}" for ext, err in failures.items()) msg += f"\nFailures:```\n{failures}```" - log.debug(f"Batch {verb}ed extensions.") + log.debug("Batch %sed extensions.", verb) await loading_message.edit(content=msg) self.action_in_progress = False @@ -203,7 +203,7 @@ async def manage(self, action: Action, ext: str) -> tuple[str, str | None]: if hasattr(e, "original"): e = e.original - log.exception(f"Extension '{ext}' failed to {verb}.") + log.exception("Extension '%s' failed to %s.", ext, verb) error_msg = f"{e.__class__.__name__}: {e}" msg = f":x: Failed to {verb} extension `{ext}`:\n```\n{error_msg}```" diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 1b386ec000..288a4b32ba 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -251,9 +251,9 @@ def ensure_valid_reminder(self, reminder: dict) -> tuple[bool, discord.TextChann if not channel: is_valid = False log.info( - f"Reminder {reminder['id']} invalid: " - f"Channel {reminder['channel_id']}={channel}." - ) + "Reminder %s invalid: " + "Channel %s=%s.", + reminder["id"], reminder["channel_id"], channel) scheduling.create_task(self.bot.api_client.delete(f"bot/reminders/{reminder['id']}")) return is_valid, channel @@ -390,12 +390,12 @@ async def send_reminder(self, reminder: dict, expected_time: time.Timestamp | No await partial_message.reply(content=f"{additional_mentions}", embed=embed) except discord.HTTPException as e: log.info( - f"There was an error when trying to reply to a reminder invocation message, {e}, " - "fall back to using jump_url" - ) + "There was an error when trying to reply to a reminder invocation message, %s, " + "fall back to using jump_url", + e) await channel.send(content=f"<@{reminder['author']}> {additional_mentions}", embed=embed) - log.debug(f"Deleting reminder #{reminder['id']} (the user has been reminded).") + log.debug("Deleting reminder #%s (the user has been reminded).", reminder["id"]) await self.bot.api_client.delete(f"bot/reminders/{reminder['id']}") @staticmethod @@ -715,11 +715,11 @@ async def _can_modify(self, ctx: Context, reminder_id: str | int, send_on_denial owner_id = api_response["author"] if owner_id == ctx.author.id: - log.debug(f"{ctx.author} is the reminder's author and passes the check.") + log.debug("%s is the reminder's author and passes the check.", ctx.author) return True if await has_any_role_check(ctx, Roles.admins): - log.debug(f"{ctx.author} is an admin, asking for confirmation to modify someone else's.") + log.debug("%s is an admin, asking for confirmation to modify someone else's.", ctx.author) if ctx.command == self.delete_reminder: modify_action = "delete" @@ -737,13 +737,13 @@ async def _can_modify(self, ctx: Context, reminder_id: str | int, send_on_denial await confirmation_message.edit(view=None) if confirmation_view.result: - log.debug(f"{ctx.author} has confirmed reminder modification.") + log.debug("%s has confirmed reminder modification.", ctx.author) else: await ctx.send("🚫 Operation canceled.") - log.debug(f"{ctx.author} has cancelled reminder modification.") + log.debug("%s has cancelled reminder modification.", ctx.author) return confirmation_view.result - log.debug(f"{ctx.author} is not the reminder's author and thus does not pass the check.") + log.debug("%s is not the reminder's author and thus does not pass the check.", ctx.author) if send_on_denial: await send_denial(ctx, "You can't modify reminders of other users!") return False diff --git a/bot/exts/utils/snekbox/_cog.py b/bot/exts/utils/snekbox/_cog.py index 4124ca097a..18b5e177fb 100644 --- a/bot/exts/utils/snekbox/_cog.py +++ b/bot/exts/utils/snekbox/_cog.py @@ -361,8 +361,11 @@ def _filter_files(self, ctx: Context, files: list[FileAttachment], blocked_exts: if blocked: blocked_str = ", ".join(f.suffix for f in blocked) log.info( - f"User '{ctx.author}' ({ctx.author.id}) uploaded blacklisted file(s) in eval: {blocked_str}", - extra={"attachment_list": [f.filename for f in files]} + "User '%s' (%s) uploaded blacklisted file(s) in eval: %s", + ctx.author, + ctx.author.id, + blocked_str, + extra={"attachment_list": [f.filename for f in files]}, ) return FilteredFiles(allowed, blocked) @@ -446,7 +449,7 @@ async def send_job(self, ctx: Context, job: EvalJob) -> Message: response = await ctx.send(msg, allowed_mentions=allowed_mentions, view=view, files=files) view.message = response - log.info(f"{ctx.author}'s {job.name} job had a return code of {result.returncode}") + log.info("%s's %s job had a return code of %s", ctx.author, job.name, result.returncode) return response async def continue_job( @@ -539,7 +542,7 @@ async def run_job( else: self.bot.stats.incr("snekbox_usages.channels.topical") - log.info(f"Received code from {ctx.author} for evaluation:\n{job}") + log.info("Received code from %s for evaluation:\n%s", ctx.author, job) while True: try: @@ -559,7 +562,7 @@ async def run_job( job = await self.continue_job(ctx, response, job.name) if not job: break - log.info(f"Re-evaluating code from message {ctx.message.id}:\n{job}") + log.info("Re-evaluating code from message %s:\n%s", ctx.message.id, job) @command( name="eval", diff --git a/bot/exts/utils/snekbox/_eval.py b/bot/exts/utils/snekbox/_eval.py index 1a077f2bbc..3783531884 100644 --- a/bot/exts/utils/snekbox/_eval.py +++ b/bot/exts/utils/snekbox/_eval.py @@ -179,7 +179,7 @@ def from_dict(cls, data: dict[str, str | int | list[dict[str, str]]]) -> EvalRes try: res.files.append(FileAttachment.from_dict(file)) except ValueError as e: - log.info(f"Failed to parse file from snekbox response: {e}") + log.info("Failed to parse file from snekbox response: %s", e) res.failed_files.append(file["path"]) return res diff --git a/bot/utils/lock.py b/bot/utils/lock.py index 2c8420a58a..170e34c07c 100644 --- a/bot/utils/lock.py +++ b/bot/utils/lock.py @@ -104,11 +104,11 @@ async def wrapper(*args, **kwargs) -> Any: # 2. `asyncio.Lock.acquire()` does not internally await anything if the lock is free # 3. awaits only yield execution to the event loop at actual I/O boundaries if wait or not lock_.locked(): - log.debug(f"{name}: acquiring lock for resource {namespace!r}:{id_!r}...") + log.debug("%s: acquiring lock for resource %r:%r...", name, namespace, id_) async with lock_: return await func(*args, **kwargs) else: - log.info(f"{name}: aborted because resource {namespace!r}:{id_!r} is locked") + log.info("%s: aborted because resource %r:%r is locked", name, namespace, id_) if raise_error: raise LockedResourceError(str(namespace), id_) return None diff --git a/bot/utils/messages.py b/bot/utils/messages.py index 57bb2c8e31..1a3f52578e 100644 --- a/bot/utils/messages.py +++ b/bot/utils/messages.py @@ -160,12 +160,12 @@ async def send_attachments( elif link_large: large.append(attachment) else: - log.info(f"{failure_msg} because it's too large.") + log.info("%s because it's too large.", failure_msg) except discord.HTTPException as e: if link_large and e.status == 413: large.append(attachment) else: - log.warning(f"{failure_msg} with status {e.status}.", exc_info=e) + log.warning("%s with status %s.", failure_msg, e.status, exc_info=e) if link_large and large: desc = "\n".join(f"[{attachment.filename}]({attachment.url})" for attachment in large)