astock-agent/backend/app/notifications/feishu.py
2026-04-23 17:24:55 +08:00

94 lines
2.4 KiB
Python

"""Feishu/Lark 告警发送"""
from __future__ import annotations
import hashlib
import logging
from datetime import datetime
from zoneinfo import ZoneInfo
import httpx
from app.config import settings
from app.data.cache import cache
logger = logging.getLogger(__name__)
def _build_signature(
source: str,
message: str,
level: str,
context: dict | None = None,
) -> str:
context = context or {}
basis = "|".join([
source,
level,
message.strip(),
str(context.get("method", "")),
str(context.get("path", "")),
])
return hashlib.sha1(basis.encode("utf-8")).hexdigest()
def _truncate(text: str, limit: int) -> str:
text = (text or "").strip()
if len(text) <= limit:
return text
return f"{text[:limit]}..."
async def send_feishu_alert(
source: str,
message: str,
detail: str = "",
level: str = "error",
context: dict | None = None,
) -> bool:
"""发送 Feishu 告警,内置去重,失败不抛异常。"""
if not settings.alert_enabled or not settings.feishu_webhook_url:
return False
signature = _build_signature(source, message, level, context)
dedup_key = f"feishu_alert:{signature}"
if cache.get(dedup_key):
return False
cache.set(dedup_key, True, settings.alert_dedup_ttl_seconds)
now = datetime.now(ZoneInfo("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S")
context = context or {}
detail = _truncate(detail, settings.alert_max_detail_chars)
lines = [
f"[{settings.alert_app_name}] {level.upper()}",
f"环境: {settings.alert_environment}",
f"时间: {now}",
f"来源: {source}",
f"摘要: {message}",
]
if context.get("method") or context.get("path"):
lines.append(
f"请求: {context.get('method', '')} {context.get('path', '')}".strip()
)
if context.get("query"):
lines.append(f"Query: {context['query']}")
if detail:
lines.append(f"详情: {detail}")
payload = {
"msg_type": "text",
"content": {
"text": "\n".join(lines),
},
}
try:
async with httpx.AsyncClient(timeout=8, follow_redirects=True) as client:
resp = await client.post(settings.feishu_webhook_url, json=payload)
resp.raise_for_status()
return True
except Exception as e:
logger.warning("Feishu 告警发送失败: %s", e)
return False