# ADR-022: Sentry 整合架構 | 項目 | 內容 | |------|------| | **狀態** | ✅ 已採用 | | **日期** | 2026-03-26 | | **決策者** | 首席架構師 + 統帥 | | **Phase** | Phase 10 | ## 背景 AWOOOI 需要完整的錯誤追蹤能力,補強 SignOz APM 的不足: - SignOz 專注效能監控 (Traces, Metrics) - Sentry 專注錯誤追蹤 (Stacktrace, Context, Session Replay) ## 決策 採用 **BFF + AI 分析** 三層架構: ``` ┌─────────────────────────────────────────────────────────────┐ │ AWOOOI Web UI │ │ /errors 頁面 (React + next-intl) │ └─────────────────────────────────────────────────────────────┘ │ ▼ ┌─────────────────────────────────────────────────────────────┐ │ AWOOOI API (BFF Layer) │ │ Router: /api/v1/errors/* │ │ Service: SentryService + ErrorAnalyzerService │ └─────────────────────────────────────────────────────────────┘ │ ┌───────────────┼───────────────┐ ▼ ▼ ┌──────────────────────┐ ┌──────────────────────────┐ │ Sentry Self-Hosted │ │ OpenClaw LLM │ │ 192.168.0.110:9000 │ │ 192.168.0.188:8088 │ └──────────────────────┘ └──────────────────────────┘ ``` ## 架構組件 ### 1. SentryService (`services/sentry_service.py`) **職責**: 封裝 Sentry API 呼叫 ```python class SentryService: async def list_projects() -> list[dict] async def list_issues(query, limit, cursor) -> list[dict] async def get_issue(issue_id) -> dict async def get_issue_events(issue_id, limit, full) -> list[dict] async def get_project_stats(stat, resolution) -> list def get_issue_url(issue_id) -> str ``` **設計原則**: - Singleton 模式 - 配置從 `core/config.py` Settings 取得 - 支援 DI 測試替換 ### 2. ErrorAnalyzerService (`services/error_analyzer_service.py`) **職責**: AI 根因分析 ```python class ErrorAnalyzerService: async def analyze_error( issue_id, title, level, culprit, count, stacktrace, context ) -> tuple[ErrorAnalysisResult | None, str, bool] ``` **分析結果**: - root_cause: 根因分析 - category: CODE_BUG | DEPENDENCY | CONFIGURATION | ... - severity: LOW | MEDIUM | HIGH | CRITICAL - fix_recommendation: 修復建議 - prevention: 預防措施 - confidence: 信心度 (0.0-1.0) ### 3. Router Layer (`api/v1/errors.py`) **職責**: HTTP 轉發 (符合 leWOOOgo 積木化) | 端點 | 功能 | |------|------| | `GET /stats` | 錯誤統計概覽 | | `GET /issues` | 列出 Issues (分頁、過濾) | | `GET /issues/{id}` | Issue 詳情 | | `GET /trends` | 趨勢圖表數據 | | `POST /issues/{id}/analyze` | 觸發 AI 分析 | ## 配置管理 所有 Sentry 配置集中於 `core/config.py`: ```python SENTRY_SELF_HOSTED_URL: str = "http://192.168.0.110:9000" SENTRY_ORG: str = "sentry" SENTRY_PROJECT: str = "awoooi-api" SENTRY_AUTH_TOKEN: str = "" # K8s Secret ``` ## 與 SignOz 的關係 | 工具 | 職責 | 查看時機 | |------|------|----------| | **SignOz** | APM + Traces | 慢、效能問題 | | **Sentry** | Error Tracking | 壞、錯誤堆疊 | **互補策略**: 兩者皆部署,各司其職。 ## 前端整合 - 使用 `next-intl` 100% i18n (禁止 hardcode) - Nothing.tech 視覺規範 (白底、細邊框、無陰影) - React Hook: `useErrors()` 自動刷新 60 秒 ## 替代方案 (已拒絕) | 方案 | 拒絕原因 | |------|----------| | 直接嵌入 Sentry Iframe | 違反視覺主權,無法自訂 UI | | 前端直接呼叫 Sentry API | 違反 BFF 原則,CORS 問題 | | 只用 SignOz | 錯誤追蹤能力不足 | ## 後果 ### 正面 - 完整的錯誤追蹤能力 - AI 輔助根因分析 - 符合 leWOOOgo 架構 ### 負面 - 多一個服務需維護 (Sentry Self-Hosted) - 額外的 API 呼叫延遲 ## 🔴 前端內網 IP 禁令 (2026-03-30 補充) ### 問題 前端 Sentry DSN 使用內網 IP (`192.168.0.110:9000`) 會觸發瀏覽器「存取區域網路」權限對話框。 ### 解決方案 **已採用**: Sentry Tunnel (方案 A) ``` 前端 → /api/sentry-tunnel → Sentry Server (192.168.0.110:9000) ↑ 公網域名 ↑ Server-Side 內網 ``` **配置位置**: - `apps/web/src/app/api/sentry-tunnel/route.ts` - Tunnel 實作 - `apps/web/sentry.client.config.ts` - `tunnel: '/api/sentry-tunnel'` ### CD Pipeline 規範 ```yaml # ❌ 禁止 (即使有 Tunnel,也不應暴露內網 IP) --build-arg NEXT_PUBLIC_SENTRY_DSN=http://...@192.168.0.110:9000/2 # ✅ 不設定 (依賴 Tunnel 機制) # 或使用公網域名反向代理 ``` ## 相關文件 - `project_phase10_arch_review.md` - 架構審查報告 - `project_sentry_full_integration.md` - 整合計畫 - `feedback_sentry_local_network.md` - 區域網路權限問題 - ADR-005: BFF Architecture - ADR-006: AI Fallback Strategy