Prechádzať zdrojové kódy

feat: add gateway audit stats

Jax Docker 1 mesiac pred
rodič
commit
2b5c24ed29

+ 8 - 0
README.md

@@ -1016,6 +1016,14 @@ Invoke-RestMethod `
   -Headers @{"x-tenant-id"="t1"}
 ```
 
+Query gateway audit stats:
+
+```powershell
+Invoke-RestMethod `
+  -Uri "http://127.0.0.1:8000/gateway/audits/stats?tenant_id=t1" `
+  -Headers @{"x-tenant-id"="t1"}
+```
+
 Gateway API Key auth:
 
 - `AGENT_PLATFORM_AUTH_REQUIRED=false` by default for local development.

+ 25 - 0
services/api-gateway/app/api/routes.py

@@ -16,6 +16,8 @@ from app.schemas.gateway import (
     ApiKeyResponse,
     ApiKeyStatusUpdateRequest,
     GatewayRequestAuditResponse,
+    GatewayAuditServiceStats,
+    GatewayAuditStatsResponse,
     GatewayServicesHealthResponse,
 )
 
@@ -105,6 +107,29 @@ def list_gateway_audits(
     return [GatewayRequestAuditResponse.from_entity(item) for item in items]
 
 
+@router.get("/gateway/audits/stats", response_model=GatewayAuditStatsResponse)
+def gateway_audit_stats(
+    tenant_id: str = Query(...),
+    db: Session = Depends(get_db),
+) -> GatewayAuditStatsResponse:
+    rows = GatewayRequestAuditRepository(db).stats_by_service(tenant_id=tenant_id)
+    services = [
+        GatewayAuditServiceStats(
+            target_service=target_service,
+            request_count=request_count,
+            error_count=error_count,
+            average_duration_ms=round(average_duration_ms, 2),
+        )
+        for target_service, request_count, error_count, average_duration_ms in rows
+    ]
+    return GatewayAuditStatsResponse(
+        tenant_id=tenant_id,
+        total_request_count=sum(item.request_count for item in services),
+        total_error_count=sum(item.error_count for item in services),
+        services=services,
+    )
+
+
 def get_gateway_settings() -> ApiGatewaySettings:
     return ApiGatewaySettings()
 

+ 31 - 1
services/api-gateway/app/domain/repositories.py

@@ -1,6 +1,6 @@
 from datetime import datetime
 
-from sqlalchemy import select
+from sqlalchemy import case, func, select
 from sqlalchemy.orm import Session
 
 from app.db.models import ApiKey, GatewayRequestAudit
@@ -61,6 +61,36 @@ class GatewayRequestAuditRepository:
         stmt = stmt.order_by(GatewayRequestAudit.created_time.desc()).limit(limit)
         return list(self.db.scalars(stmt))
 
+    def stats_by_service(self, *, tenant_id: str) -> list[tuple[str, int, int, float]]:
+        target_service = func.coalesce(GatewayRequestAudit.target_service, "api-gateway")
+        error_count = func.sum(
+            case(
+                (GatewayRequestAudit.status_code >= 400, 1),
+                else_=0,
+            )
+        )
+        stmt = (
+            select(
+                target_service.label("target_service"),
+                func.count(GatewayRequestAudit.id),
+                error_count,
+                func.avg(GatewayRequestAudit.duration_ms),
+            )
+            .where(GatewayRequestAudit.tenant_id == tenant_id)
+            .group_by(target_service)
+            .order_by(target_service.asc())
+        )
+        rows = self.db.execute(stmt).all()
+        return [
+            (
+                str(row[0]),
+                int(row[1] or 0),
+                int(row[2] or 0),
+                float(row[3] or 0.0),
+            )
+            for row in rows
+        ]
+
 
 class ApiKeyRepository:
     def __init__(self, db: Session) -> None:

+ 14 - 0
services/api-gateway/app/schemas/gateway.py

@@ -43,6 +43,20 @@ class GatewayRequestAuditResponse(BaseModel):
         return cls.model_validate(entity, from_attributes=True)
 
 
+class GatewayAuditServiceStats(BaseModel):
+    target_service: str
+    request_count: int
+    error_count: int
+    average_duration_ms: float
+
+
+class GatewayAuditStatsResponse(BaseModel):
+    tenant_id: str
+    total_request_count: int
+    total_error_count: int
+    services: list[GatewayAuditServiceStats]
+
+
 class ApiKeyCreateRequest(BaseModel):
     tenant_id: str
     name: str