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:
@@ -24,6 +24,8 @@ dependencies = [
|
||||
"opentelemetry-instrumentation-fastapi>=0.41b0",
|
||||
"opentelemetry-instrumentation-httpx>=0.41b0",
|
||||
"opentelemetry-instrumentation-logging>=0.41b0",
|
||||
# Sentry (Error Tracking - 補強 SignOz,Self-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 註解
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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 Tracking,SignOz 專注 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",
|
||||
|
||||
Reference in New Issue
Block a user