```
feat: 添加数据库迁移脚本并更新Dockerfile配置 - 在Dockerfile中添加alembic配置文件和目录的复制指令 - 更新alembic/env.py注册新的模块模型:events、workflows、writebacks - 生成完整的初始数据库schema迁移脚本,包含以下表: - approval_requests, attendance_records, audit_logs, domain_events - expenses, feishu_event_receipts, fund_accounts, legacy_sync_runs - official_writeback_runs, performance_metrics, policies, procurements - projects, report_push_runs, risk_event_actions, risk_events - standards, suppliers, work_reports, work_tasks, workflow_actions - workflow_instances等21个数据表结构定义 - 在API路由器中添加新模块的路由:events、workflows、writebacks、observability ```
This commit is contained in:
1
app/modules/observability/__init__.py
Normal file
1
app/modules/observability/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
27
app/modules/observability/constants.py
Normal file
27
app/modules/observability/constants.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from enum import StrEnum
|
||||
|
||||
|
||||
class ObservabilityKey(StrEnum):
|
||||
STATUS = "status"
|
||||
CHECKS = "checks"
|
||||
METRICS = "metrics"
|
||||
DATABASE = "database"
|
||||
REDIS = "redis"
|
||||
EVENTS = "events"
|
||||
WORKFLOWS = "workflows"
|
||||
WRITEBACKS = "writebacks"
|
||||
|
||||
|
||||
class ObservabilityStatus(StrEnum):
|
||||
OK = "ok"
|
||||
DEGRADED = "degraded"
|
||||
SKIPPED = "skipped"
|
||||
ERROR = "error"
|
||||
|
||||
|
||||
class ObservabilityMetricKey(StrEnum):
|
||||
ERROR = "error"
|
||||
PENDING = "pending"
|
||||
FAILED = "failed"
|
||||
RUNNING = "running"
|
||||
DISABLED = "disabled"
|
||||
30
app/modules/observability/routes.py
Normal file
30
app/modules/observability/routes.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.database import get_db
|
||||
from app.core.security import require_api_key
|
||||
from app.modules.observability.service import ObservabilityService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get("/health/live")
|
||||
def live(
|
||||
db: Session = Depends(get_db),
|
||||
) -> dict:
|
||||
_ = db
|
||||
return ObservabilityService(db).live()
|
||||
|
||||
|
||||
@router.get("/health/ready")
|
||||
def ready(
|
||||
db: Session = Depends(get_db),
|
||||
) -> dict:
|
||||
return ObservabilityService(db).ready()
|
||||
|
||||
|
||||
@router.get("/metrics", dependencies=[Depends(require_api_key)])
|
||||
def metrics(
|
||||
db: Session = Depends(get_db),
|
||||
) -> dict:
|
||||
return ObservabilityService(db).metrics()
|
||||
114
app/modules/observability/service.py
Normal file
114
app/modules/observability/service.py
Normal file
@@ -0,0 +1,114 @@
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.core.config import get_settings
|
||||
from app.modules.events.constants import EventStatus
|
||||
from app.modules.events.service import EventService
|
||||
from app.modules.observability.constants import (
|
||||
ObservabilityKey,
|
||||
ObservabilityMetricKey,
|
||||
ObservabilityStatus,
|
||||
)
|
||||
from app.modules.workflows.constants import WorkflowStatus
|
||||
from app.modules.workflows.service import WorkflowService
|
||||
from app.modules.writebacks.constants import WritebackStatus
|
||||
from app.modules.writebacks.service import WritebackService
|
||||
|
||||
|
||||
class ObservabilityService:
|
||||
"""Build health, readiness, and JSON metrics for V3 operations."""
|
||||
|
||||
def __init__(self, db: Session):
|
||||
self.db = db
|
||||
|
||||
def live(self) -> dict[str, str]:
|
||||
return {ObservabilityKey.STATUS: ObservabilityStatus.OK}
|
||||
|
||||
def ready(self) -> dict[str, Any]:
|
||||
checks = {
|
||||
ObservabilityKey.DATABASE: self._database_check(),
|
||||
ObservabilityKey.REDIS: self._redis_check(),
|
||||
ObservabilityKey.EVENTS: self._events_check(),
|
||||
ObservabilityKey.WORKFLOWS: self._workflows_check(),
|
||||
ObservabilityKey.WRITEBACKS: self._writebacks_check(),
|
||||
}
|
||||
degraded = any(
|
||||
item[ObservabilityKey.STATUS]
|
||||
in {ObservabilityStatus.DEGRADED, ObservabilityStatus.ERROR}
|
||||
for item in checks.values()
|
||||
)
|
||||
return {
|
||||
ObservabilityKey.STATUS: (
|
||||
ObservabilityStatus.DEGRADED if degraded else ObservabilityStatus.OK
|
||||
),
|
||||
ObservabilityKey.CHECKS: checks,
|
||||
}
|
||||
|
||||
def metrics(self) -> dict[str, Any]:
|
||||
return {
|
||||
ObservabilityKey.METRICS: {
|
||||
ObservabilityKey.EVENTS: EventService(self.db).count_by_status(),
|
||||
ObservabilityKey.WORKFLOWS: WorkflowService(self.db).count_by_status(),
|
||||
ObservabilityKey.WRITEBACKS: WritebackService(self.db).count_by_status(),
|
||||
}
|
||||
}
|
||||
|
||||
def _database_check(self) -> dict[str, Any]:
|
||||
try:
|
||||
self.db.execute(text("select 1")).scalar()
|
||||
except Exception as exc:
|
||||
return {
|
||||
ObservabilityKey.STATUS: ObservabilityStatus.ERROR,
|
||||
ObservabilityMetricKey.ERROR: str(exc),
|
||||
}
|
||||
return {ObservabilityKey.STATUS: ObservabilityStatus.OK}
|
||||
|
||||
def _redis_check(self) -> dict[str, Any]:
|
||||
settings = get_settings()
|
||||
if not settings.task_queue_enabled:
|
||||
return {ObservabilityKey.STATUS: ObservabilityStatus.SKIPPED}
|
||||
try:
|
||||
from redis import Redis
|
||||
|
||||
Redis.from_url(settings.redis_url, socket_connect_timeout=1).ping()
|
||||
except Exception as exc:
|
||||
return {
|
||||
ObservabilityKey.STATUS: ObservabilityStatus.DEGRADED,
|
||||
ObservabilityMetricKey.ERROR: str(exc),
|
||||
}
|
||||
return {ObservabilityKey.STATUS: ObservabilityStatus.OK}
|
||||
|
||||
def _events_check(self) -> dict[str, Any]:
|
||||
counts = EventService(self.db).count_by_status()
|
||||
failed = counts.get(EventStatus.FAILED, 0)
|
||||
return {
|
||||
ObservabilityKey.STATUS: (
|
||||
ObservabilityStatus.DEGRADED if failed else ObservabilityStatus.OK
|
||||
),
|
||||
ObservabilityMetricKey.PENDING: counts.get(EventStatus.PENDING, 0),
|
||||
ObservabilityMetricKey.FAILED: failed,
|
||||
}
|
||||
|
||||
def _workflows_check(self) -> dict[str, Any]:
|
||||
counts = WorkflowService(self.db).count_by_status()
|
||||
failed = counts.get(WorkflowStatus.FAILED, 0)
|
||||
return {
|
||||
ObservabilityKey.STATUS: (
|
||||
ObservabilityStatus.DEGRADED if failed else ObservabilityStatus.OK
|
||||
),
|
||||
ObservabilityMetricKey.RUNNING: counts.get(WorkflowStatus.RUNNING, 0),
|
||||
ObservabilityMetricKey.FAILED: failed,
|
||||
}
|
||||
|
||||
def _writebacks_check(self) -> dict[str, Any]:
|
||||
counts = WritebackService(self.db).count_by_status()
|
||||
failed = counts.get(WritebackStatus.FAILED, 0)
|
||||
return {
|
||||
ObservabilityKey.STATUS: (
|
||||
ObservabilityStatus.DEGRADED if failed else ObservabilityStatus.OK
|
||||
),
|
||||
ObservabilityMetricKey.DISABLED: counts.get(WritebackStatus.DISABLED, 0),
|
||||
ObservabilityMetricKey.FAILED: failed,
|
||||
}
|
||||
Reference in New Issue
Block a user