refactor(api): Sentry dedup 邏輯移至 Service 層 (leWOOOgo 模組化)
Phase 10.2.1 - 2026-03-27 台北時區 - 將 check_sentry_dedup() 從 Router 移至 SentryService.check_dedup() - Router 層禁止直接存取 Redis (遵循 leWOOOgo 積木化原則) - 保持 10 分鐘 TTL 去重窗口 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -29,6 +29,7 @@ from src.models.approval import (
|
||||
RiskLevel,
|
||||
)
|
||||
from src.services.approval_db import get_approval_service
|
||||
from src.services.sentry_service import get_sentry_service
|
||||
from src.services.telegram_gateway import get_telegram_gateway
|
||||
from src.utils.timezone import now_taipei_iso
|
||||
|
||||
@@ -48,6 +49,9 @@ SENTRY_LEVEL_TO_RISK = {
|
||||
"info": RiskLevel.LOW,
|
||||
}
|
||||
|
||||
# 去重配置 (Phase 10.2.1 - 2026-03-27)
|
||||
SENTRY_DEDUP_TTL = 600 # 10 分鐘內不重複發送同一 issue
|
||||
|
||||
|
||||
class SentryIssuePayload(BaseModel):
|
||||
"""Sentry Issue Alert Payload (簡化版)"""
|
||||
@@ -94,6 +98,12 @@ async def handle_sentry_error(
|
||||
|
||||
# 提取錯誤資訊
|
||||
issue_data = payload.get("data", {}).get("issue", {})
|
||||
|
||||
# Phase 10.2.1: 去重檢查 (10 分鐘內不重複發送)
|
||||
issue_id = issue_data.get("id")
|
||||
sentry_service = get_sentry_service()
|
||||
if not await sentry_service.check_dedup(issue_id, ttl=SENTRY_DEDUP_TTL):
|
||||
return {"status": "deduplicated", "issue_id": issue_id, "ttl": SENTRY_DEDUP_TTL}
|
||||
event_data = payload.get("data", {}).get("event", {})
|
||||
|
||||
error_context = {
|
||||
|
||||
@@ -196,6 +196,40 @@ class SentryService:
|
||||
"""取得 Issue 在 Sentry UI 的連結"""
|
||||
return f"{self.base_url}/organizations/{self.org}/issues/{issue_id}/"
|
||||
|
||||
# =========================================================================
|
||||
# Dedup (Phase 10.2.1 - 2026-03-27)
|
||||
# =========================================================================
|
||||
|
||||
async def check_dedup(self, issue_id: str, ttl: int = 600) -> bool:
|
||||
"""
|
||||
檢查 Sentry Issue 是否已在去重窗口內
|
||||
|
||||
Args:
|
||||
issue_id: Sentry Issue ID
|
||||
ttl: 去重窗口秒數 (預設 10 分鐘)
|
||||
|
||||
Returns:
|
||||
bool: True = 應處理, False = 已去重跳過
|
||||
"""
|
||||
from src.core.redis_client import get_redis_pool
|
||||
|
||||
if not issue_id:
|
||||
return True
|
||||
|
||||
redis = await get_redis_pool()
|
||||
key = f"sentry_dedup:{issue_id}"
|
||||
|
||||
# 檢查是否已存在
|
||||
exists = await redis.exists(key)
|
||||
if exists:
|
||||
logger.info("sentry_dedup_hit", issue_id=issue_id)
|
||||
return False
|
||||
|
||||
# 設置去重標記
|
||||
await redis.setex(key, ttl, "1")
|
||||
logger.debug("sentry_dedup_set", issue_id=issue_id, ttl=ttl)
|
||||
return True
|
||||
|
||||
|
||||
# =============================================================================
|
||||
# Singleton
|
||||
|
||||
Reference in New Issue
Block a user