fix(telegram): shorten polling network errors

This commit is contained in:
Xubin Ren 2026-04-04 10:26:58 +00:00
parent b9ee236ca1
commit 549e5ea8e2
2 changed files with 52 additions and 6 deletions

View File

@ -12,7 +12,7 @@ from typing import Any, Literal
from loguru import logger
from pydantic import Field
from telegram import BotCommand, ReactionTypeEmoji, ReplyParameters, Update
from telegram.error import BadRequest, TimedOut
from telegram.error import BadRequest, NetworkError, TimedOut
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
from telegram.request import HTTPXRequest
@ -325,7 +325,8 @@ class TelegramChannel(BaseChannel):
# Start polling (this runs until stopped)
await self._app.updater.start_polling(
allowed_updates=["message"],
drop_pending_updates=False # Process pending messages on startup
drop_pending_updates=False, # Process pending messages on startup
error_callback=self._on_polling_error,
)
# Keep running until stopped
@ -974,14 +975,36 @@ class TelegramChannel(BaseChannel):
except Exception as e:
logger.debug("Typing indicator stopped for {}: {}", chat_id, e)
@staticmethod
def _format_telegram_error(exc: Exception) -> str:
"""Return a short, readable error summary for logs."""
text = str(exc).strip()
if text:
return text
if exc.__cause__ is not None:
cause = exc.__cause__
cause_text = str(cause).strip()
if cause_text:
return f"{exc.__class__.__name__} ({cause_text})"
return f"{exc.__class__.__name__} ({cause.__class__.__name__})"
return exc.__class__.__name__
def _on_polling_error(self, exc: Exception) -> None:
"""Keep long-polling network failures to a single readable line."""
summary = self._format_telegram_error(exc)
if isinstance(exc, (NetworkError, TimedOut)):
logger.warning("Telegram polling network issue: {}", summary)
else:
logger.error("Telegram polling error: {}", summary)
async def _on_error(self, update: object, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Log polling / handler errors instead of silently swallowing them."""
from telegram.error import NetworkError, TimedOut
summary = self._format_telegram_error(context.error)
if isinstance(context.error, (NetworkError, TimedOut)):
logger.warning("Telegram network issue: {}", str(context.error))
logger.warning("Telegram network issue: {}", summary)
else:
logger.error("Telegram error: {}", context.error)
logger.error("Telegram error: {}", summary)
def _get_extension(
self,

View File

@ -32,8 +32,10 @@ class _FakeHTTPXRequest:
class _FakeUpdater:
def __init__(self, on_start_polling) -> None:
self._on_start_polling = on_start_polling
self.start_polling_kwargs = None
async def start_polling(self, **kwargs) -> None:
self.start_polling_kwargs = kwargs
self._on_start_polling()
@ -184,6 +186,7 @@ async def test_start_creates_separate_pools_with_proxy(monkeypatch) -> None:
assert poll_req.kwargs["connection_pool_size"] == 4
assert builder.request_value is api_req
assert builder.get_updates_request_value is poll_req
assert callable(app.updater.start_polling_kwargs["error_callback"])
assert any(cmd.command == "status" for cmd in app.bot.commands)
assert any(cmd.command == "dream" for cmd in app.bot.commands)
assert any(cmd.command == "dream-log" for cmd in app.bot.commands)
@ -307,6 +310,26 @@ async def test_on_error_logs_network_issues_as_warning(monkeypatch) -> None:
assert recorded == [("warning", "Telegram network issue: proxy disconnected")]
@pytest.mark.asyncio
async def test_on_error_summarizes_empty_network_error(monkeypatch) -> None:
from telegram.error import NetworkError
channel = TelegramChannel(
TelegramConfig(enabled=True, token="123:abc", allow_from=["*"]),
MessageBus(),
)
recorded: list[tuple[str, str]] = []
monkeypatch.setattr(
"nanobot.channels.telegram.logger.warning",
lambda message, error: recorded.append(("warning", message.format(error))),
)
await channel._on_error(object(), SimpleNamespace(error=NetworkError("")))
assert recorded == [("warning", "Telegram network issue: NetworkError")]
@pytest.mark.asyncio
async def test_on_error_keeps_non_network_exceptions_as_error(monkeypatch) -> None:
channel = TelegramChannel(