From 4d89cda7bd29390fd519bdf2a062d5400837f5db Mon Sep 17 00:00:00 2001 From: Leander Furumo Date: Tue, 3 Nov 2026 01:42:21 +0100 Subject: [PATCH] log background loop errors Co-Authored-By: Claude Opus 5.5 --- bot/src/cogs/galtinn.py | 2 ++ bot/src/cogs/utils/misc_utils.py | 37 ++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/bot/src/cogs/galtinn.py b/bot/src/cogs/galtinn.py index 2d51053..4797000 100644 --- a/bot/src/cogs/galtinn.py +++ b/bot/src/cogs/galtinn.py @@ -402,6 +402,7 @@ class Galtinn(commands.Cog): return success @tasks.loop(time=misc_utils.MIDNIGHT) + @misc_utils.log_errors async def membership_check(self): """ Checks all registered user's membership status and assigns roles based on the status @@ -463,6 +464,7 @@ class Galtinn(commands.Cog): await self.bot.wait_until_ready() @tasks.loop(minutes=2) + @misc_utils.log_errors async def verification_cleanup(self): """ Clean up any pending verifications that have expired diff --git a/bot/src/cogs/utils/misc_utils.py b/bot/src/cogs/utils/misc_utils.py index 12e8e07..6a246c9 100644 --- a/bot/src/cogs/utils/misc_utils.py +++ b/bot/src/cogs/utils/misc_utils.py @@ -1,9 +1,46 @@ +import asyncio import datetime +import functools from math import ceil from zoneinfo import ZoneInfo +import aiohttp +import discord + MIDNIGHT = datetime.time(hour=0, minute=0, tzinfo=ZoneInfo("Europe/Oslo")) +# The exceptions discord.py's tasks.loop retries instead of stopping on +RETRIED_TASK_EXCEPTIONS = ( + OSError, + discord.GatewayNotFound, + discord.ConnectionClosed, + aiohttp.ClientError, + asyncio.TimeoutError, +) + + +def log_errors(func): + """ + Decorator for background task loops that logs exceptions instead of letting them stop the loop. + Network errors are re-raised so discord.py can retry the iteration like it normally does. + Put it below the @tasks.loop decorator. The decorated method's class needs a `bot` attribute + + Parameters + ---------- + func (callable): The loop's coroutine function + """ + + @functools.wraps(func) + async def wrapper(self, *args, **kwargs): + try: + await func(self, *args, **kwargs) + except RETRIED_TASK_EXCEPTIONS: + raise + except Exception: + self.bot.logger.exception(f"Unhandled error in background task {func.__qualname__}") + + return wrapper + class Paginator: """Interface for managing content. Divides your content into pages of 10 items each"""