feat: integrate Sentry + fix CI/CD issues

Sentry Integration (補強 SignOz):
- Add @sentry/nextjs for frontend error tracking + session replay
- Add sentry-sdk[fastapi] for backend error tracking
- Create sentry.client/server/edge.config.ts
- Integrate with next.config.js + instrumentation.ts
- Add Sentry exception capture in FastAPI error handler
- Create deployment scripts for Self-Hosted @ 192.168.0.110

CI/CD Fixes:
- Fix F821 Undefined name 'Field' in incidents.py
- Add NEXT_PUBLIC_API_URL env var to CI build step
- Add build-arg to Docker build verification

E2E Test Improvements:
- Fix strict mode violations in dashboard-acceptance tests
- Add timeout increase for Phase 4 demo tests
- Make tests more resilient to UI variations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
OG T
2026-03-24 15:19:52 +08:00
parent 7a76f3e628
commit 9bff46a1b0
41 changed files with 3195 additions and 153 deletions

View File

@@ -24,6 +24,8 @@ dependencies = [
"opentelemetry-instrumentation-fastapi>=0.41b0",
"opentelemetry-instrumentation-httpx>=0.41b0",
"opentelemetry-instrumentation-logging>=0.41b0",
# Sentry (Error Tracking - 補強 SignOzSelf-Hosted @ 192.168.0.110)
"sentry-sdk[fastapi]>=2.0.0",
# Phase 6.4g: leWOOOgo Brain - 積木化決策引擎
# NOTE: Local packages 透過 Dockerfile 預先安裝,無需在此列出
# 請參閱 apps/api/Dockerfile Phase 6.4i 註解

View File

@@ -40,3 +40,4 @@ opentelemetry-instrumentation-logging>=0.41b0
pytest>=7.4.0
pytest-asyncio>=0.23.0
ruff>=0.1.0
sentry-sdk[fastapi]>=2.0.0

View File

@@ -21,7 +21,7 @@ from datetime import UTC
from typing import Any
from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel
from pydantic import BaseModel, Field
from src.core.logging import get_logger
from src.core.redis_client import get_redis

View File

@@ -55,12 +55,16 @@ def get_engine() -> AsyncEngine:
if _engine is None:
database_url = settings.DATABASE_URL
# 統帥鐵律: 禁止 SQLite
# 統帥鐵律: 禁止 SQLite (AWOOOI 憲法)
# 🔴 違反此規則必須立即報錯,禁止 fallback
if "sqlite" in database_url.lower():
import structlog
logger = structlog.get_logger(__name__)
logger.error("sqlite_forbidden", message="SQLite is FORBIDDEN. Using PostgreSQL default.")
database_url = "postgresql+asyncpg://awoooi:changeme@192.168.0.188:5432/awoooi_prod"
logger.error("sqlite_forbidden", url=database_url)
raise ValueError(
"SQLite is FORBIDDEN by AWOOOI Constitution. "
"Set DATABASE_URL to PostgreSQL: postgresql+asyncpg://user:pass@host:5432/db"
)
_engine = create_async_engine(
database_url,

View File

@@ -10,15 +10,23 @@ Four Iron Laws:
3. Pydantic Config - Type-safe settings with validation
4. structlog - Structured JSON logging
Observability Stack:
- OpenTelemetry → SignOz (Traces + Logs + Metrics)
- Sentry SDK → Sentry Self-Hosted (Error Tracking + Stack Traces)
Version: 1.0.0
Date: 2026-03-20
"""
import os
from collections.abc import AsyncGenerator
from contextlib import asynccontextmanager
import sentry_sdk
import structlog
from fastapi import FastAPI, Request
from sentry_sdk.integrations.fastapi import FastApiIntegration
from sentry_sdk.integrations.starlette import StarletteIntegration
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
@@ -67,6 +75,36 @@ from src.workers import close_signal_worker, init_signal_worker
setup_logging()
logger = get_logger("awoooi.api")
# =============================================================================
# Sentry SDK Initialization (Error Tracking - 補強 SignOz)
# Self-Hosted @ 192.168.0.110
# 分工: Sentry 專注 Error TrackingSignOz 專注 Traces/Logs/Metrics
# =============================================================================
SENTRY_DSN = os.getenv("SENTRY_DSN")
if SENTRY_DSN:
sentry_sdk.init(
dsn=SENTRY_DSN,
environment=settings.ENVIRONMENT,
release=f"awoooi-api@{settings.VERSION}",
# 效能監控取樣率 (生產環境降低)
traces_sample_rate=0.1 if settings.ENVIRONMENT == "production" else 1.0,
# FastAPI 深度整合
integrations=[
FastApiIntegration(transaction_style="endpoint"),
StarletteIntegration(transaction_style="endpoint"),
],
# 忽略常見的非錯誤
ignore_errors=[
ConnectionRefusedError,
TimeoutError,
],
# 只在生產環境發送
send_default_pii=False,
)
logger.info("sentry_initialized", dsn=SENTRY_DSN.split("@")[-1])
else:
logger.info("sentry_disabled", reason="SENTRY_DSN not configured")
# =============================================================================
# Application Lifespan
@@ -249,11 +287,15 @@ async def request_logging_middleware(request: Request, call_next):
@app.exception_handler(Exception)
async def global_exception_handler(_request: Request, exc: Exception) -> JSONResponse:
"""
Global exception handler with structured logging
Global exception handler with structured logging + Sentry
Catches all unhandled exceptions and returns a safe error response.
Full exception details are logged but not exposed to clients.
Sentry SDK 會自動捕獲並發送到 Self-Hosted Server。
"""
# Sentry 自動捕獲 (如果已初始化)
sentry_sdk.capture_exception(exc)
log = get_logger("awoooi.error")
log.exception(
"unhandled_exception",