From 37d8a30e5cbae251cc5aeb1795b9062e5963dae5 Mon Sep 17 00:00:00 2001 From: Alex Kulikov Date: Sat, 25 Jul 2026 01:01:58 +0100 Subject: [PATCH] fix: stop sender's own send-failure logs from re-triggering broadcast logger.error() calls inside TelegramSender's exception handling were picked up by the root-attached ErrorBroadcastHandler and re-sent to error_logs_recipients. When that broadcast itself failed (e.g. Telegram flood control), the failure was logged the same way, causing an unbounded feedback loop of error messages to the same chat. Mark these specific logs with skip_broadcast so delivery failures are logged locally but no longer re-enter the broadcast pipeline. Co-Authored-By: Claude Sonnet 5 --- src/tg/sender.py | 18 +++++++++++++++++- src/utils/log_handler.py | 10 +++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/src/tg/sender.py b/src/tg/sender.py index 77a1e40..0280696 100644 --- a/src/tg/sender.py +++ b/src/tg/sender.py @@ -176,7 +176,15 @@ async def _send_to_chat_id_async( ) return True except telegram.error.TelegramError as e: - logger.error(f"Could not send a message to {chat_id}", exc_info=e) + # skip_broadcast: this failure is already surfaced explicitly + # below (redirected to error_logs_recipients with the message + # content); broadcasting it again via ErrorBroadcastHandler + # would re-enter this same send path. + logger.error( + f"Could not send a message to {chat_id}", + exc_info=e, + extra={"skip_broadcast": True}, + ) # Captured now: the "except ... as e" binding is cleared by Python # when its except block exits, and the inner except below rebinds # its own "e" -- reading e.message after the loop would otherwise @@ -200,10 +208,15 @@ async def _send_to_chat_id_async( **kwargs, ) except telegram.error.TelegramError as redirect_error: + # skip_broadcast: broadcasting this would try to + # re-notify the very error_logs_recipients that just + # failed, which is how a single flood-control hit + # turns into a runaway retry storm. logger.error( "Could not redirect unsended message " f"to error_logs_recipients {error_logs_recipient}", exc_info=redirect_error, + extra={"skip_broadcast": True}, ) # HTML parse error isn't a separate class in Telegram @@ -225,9 +238,12 @@ async def _send_to_chat_id_async( ) return True except telegram.error.TelegramError as e: + # skip_broadcast: same rationale as above -- this is a + # failure of the error-notification path itself. logger.error( f"Could not send a plain-text message to {chat_id}", exc_info=e, + extra={"skip_broadcast": True}, ) return False diff --git a/src/utils/log_handler.py b/src/utils/log_handler.py index 55fdfc0..7f51d04 100644 --- a/src/utils/log_handler.py +++ b/src/utils/log_handler.py @@ -43,7 +43,15 @@ def emit(self, record: LogRecord): exc_info=e, ) ) - if record.levelno >= ERROR and not self.is_muted: + # skip_broadcast lets TelegramSender's own send-failure logs opt out: + # broadcasting a Telegram-delivery failure over Telegram is circular + # and, under flood control, snowballs into a self-sustaining error + # storm (each failed broadcast attempt logs another ERROR). + if ( + record.levelno >= ERROR + and not self.is_muted + and not getattr(record, "skip_broadcast", False) + ): error_message = f"{record.levelname} - {record.module} - {record.message}" if record.exc_text: error_message += f" - {record.exc_text}"