fix(telegram): preserve incident history html output
All checks were successful
Code Review / ai-code-review (push) Successful in 11s
CD Pipeline / tests (push) Successful in 1m1s
CD Pipeline / build-and-deploy (push) Successful in 3m27s
CD Pipeline / post-deploy-checks (push) Successful in 1m29s

This commit is contained in:
Your Name
2026-05-14 23:33:43 +08:00
parent f4a8390dc0
commit 65001da0d8
6 changed files with 218 additions and 5 deletions

View File

@@ -70,6 +70,7 @@ logger = structlog.get_logger(__name__)
_TELEGRAM_BOT_URL_RE = re.compile(r"(api\.telegram\.org/bot)[^/\s]+")
_INCIDENT_ID_RE = re.compile(r"\bINC-\d{8}-[A-Z0-9]{4,}\b")
_CODE_REF_RE = re.compile(r"<code>([0-9a-f]{7,12})</code>", re.IGNORECASE)
_TELEGRAM_HTML_CHUNK_LIMIT = 3600
def _top_gateway_bucket(
@@ -195,6 +196,34 @@ def _format_remediation_history_lines(history: dict[str, object] | None) -> list
]
def _telegram_html_chunks(lines: list[str], limit: int = _TELEGRAM_HTML_CHUNK_LIMIT) -> list[str]:
"""Split HTML messages by complete lines so Telegram does not receive broken tags."""
chunks: list[str] = []
current: list[str] = []
current_len = 0
for raw_line in lines:
line = str(raw_line)
line_len = len(line) + 1
if current and current_len + line_len > limit:
chunks.append("\n".join(current))
current = []
current_len = 0
if line_len > limit:
chunks.append(line[:limit])
continue
current.append(line)
current_len += line_len
if current:
chunks.append("\n".join(current))
return chunks
def _plain_text_from_html(text: str, limit: int = 3900) -> str:
"""Fallback renderer for Telegram HTML parse failures."""
plain = re.sub(r"</?[^>]+>", "", text)
return html.unescape(plain)[:limit]
def _sanitize_telegram_error(text: str) -> str:
"""遮蔽 Telegram Bot URL 中的 token避免例外字串污染 log / trace。"""
return _TELEGRAM_BOT_URL_RE.sub(r"\1<redacted>", text)
@@ -5366,7 +5395,10 @@ class TelegramGateway:
error=str(remediation_exc),
)
await self.send_notification("\n".join(lines))
await self._send_html_line_message(
lines,
failure_context="incident_detail",
)
except Exception as e:
logger.warning("send_incident_detail_failed", incident_id=incident_id, error=str(e))
@@ -5501,7 +5533,10 @@ class TelegramGateway:
error=str(truth_exc),
)
await self.send_notification("\n".join(lines))
await self._send_html_line_message(
lines,
failure_context="incident_history",
)
except Exception as e:
logger.warning("send_incident_history_failed", incident_id=incident_id, error=str(e))
@@ -5714,6 +5749,41 @@ class TelegramGateway:
return await self._send_request("sendMessage", payload)
async def _send_html_line_message(
self,
lines: list[str],
*,
chat_id: str | int | None = None,
failure_context: str,
) -> None:
"""Send a multi-line HTML message without cutting Telegram tags in half."""
chunks = _telegram_html_chunks(lines)
for index, chunk in enumerate(chunks):
try:
await self._send_request(
"sendMessage",
{
"chat_id": chat_id or self.alert_chat_id,
"text": chunk,
"parse_mode": "HTML",
},
)
except Exception as exc:
logger.warning(
"telegram_html_line_message_failed",
failure_context=failure_context,
chunk_index=index,
chunk_count=len(chunks),
error=str(exc),
)
await self._send_request(
"sendMessage",
{
"chat_id": chat_id or self.alert_chat_id,
"text": _plain_text_from_html(chunk),
},
)
async def send_alert_notification(
self,
text: str,