添加日志记录和异常处理以增强错误管理

This commit is contained in:
2026-02-24 17:02:56 +08:00
parent 1b1b0a3697
commit 6fc3aa5209
3 changed files with 112 additions and 38 deletions

View File

@@ -1,6 +1,8 @@
import logging
from fastapi import APIRouter, Depends, HTTPException, status
from psycopg import AsyncConnection
from sqlalchemy import text
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.project_dependencies import (
@@ -20,6 +22,7 @@ from app.domain.schemas.metadata import (
from app.infra.repositories.metadata_repository import MetadataRepository
router = APIRouter()
logger = logging.getLogger(__name__)
@router.get("/meta/project", response_model=ProjectMetaResponse)
@@ -61,7 +64,18 @@ async def list_user_projects(
current_user=Depends(get_current_metadata_user),
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
):
projects = await metadata_repo.list_projects_for_user(current_user.id)
try:
projects = await metadata_repo.list_projects_for_user(current_user.id)
except SQLAlchemyError as exc:
logger.error(
"Metadata DB error while listing projects for user %s",
current_user.id,
exc_info=True,
)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=f"Metadata database error: {exc}",
) from exc
return [
ProjectSummaryResponse(
project_id=project.project_id,

View File

@@ -1,7 +1,9 @@
from dataclasses import dataclass
from uuid import UUID
import logging
from fastapi import Depends, HTTPException, status
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.keycloak_dependencies import get_current_keycloak_sub
@@ -9,6 +11,8 @@ from app.core.config import settings
from app.infra.db.metadata.database import get_metadata_session
from app.infra.repositories.metadata_repository import MetadataRepository
logger = logging.getLogger(__name__)
async def get_metadata_repository(
session: AsyncSession = Depends(get_metadata_session),
@@ -20,7 +24,17 @@ async def get_current_metadata_user(
keycloak_sub: UUID = Depends(get_current_keycloak_sub),
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
):
user = await metadata_repo.get_user_by_keycloak_id(keycloak_sub)
try:
user = await metadata_repo.get_user_by_keycloak_id(keycloak_sub)
except SQLAlchemyError as exc:
logger.error(
"Metadata DB error while resolving current user",
exc_info=True,
)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=f"Metadata database error: {exc}",
) from exc
if not user or not user.is_active:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user"

View File

@@ -2,8 +2,10 @@ from dataclasses import dataclass
from typing import AsyncGenerator
from uuid import UUID
import logging
from fastapi import Depends, Header, HTTPException, status
from psycopg import AsyncConnection
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.keycloak_dependencies import get_current_keycloak_sub
@@ -12,11 +14,13 @@ from app.infra.db.dynamic_manager import project_connection_manager
from app.infra.db.metadata.database import get_metadata_session
from app.infra.repositories.metadata_repository import MetadataRepository
DB_ROLE_BIZ_PG = "biz_pg"
DB_ROLE_IOT_TS = "iot_ts"
DB_ROLE_BIZ_DATA = "biz_data"
DB_ROLE_IOT_DATA = "iot_data"
DB_TYPE_POSTGRES = "postgresql"
DB_TYPE_TIMESCALE = "timescaledb"
logger = logging.getLogger(__name__)
@dataclass(frozen=True)
class ProjectContext:
@@ -43,31 +47,43 @@ async def get_project_context(
status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid project id"
) from exc
project = await metadata_repo.get_project_by_id(project_uuid)
if not project:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
)
if project.status != "active":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Project is not active"
)
try:
project = await metadata_repo.get_project_by_id(project_uuid)
if not project:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
)
if project.status != "active":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Project is not active"
)
user = await metadata_repo.get_user_by_keycloak_id(keycloak_sub)
if not user:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="User not registered"
)
if not user.is_active:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user"
)
user = await metadata_repo.get_user_by_keycloak_id(keycloak_sub)
if not user:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="User not registered"
)
if not user.is_active:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user"
)
membership_role = await metadata_repo.get_membership_role(project_uuid, user.id)
if not membership_role:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="No access to project"
membership_role = await metadata_repo.get_membership_role(
project_uuid, user.id
)
if not membership_role:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="No access to project"
)
except SQLAlchemyError as exc:
logger.error(
"Metadata DB error while resolving project context",
exc_info=True,
)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=f"Metadata database error: {exc}",
) from exc
return ProjectContext(
project_id=project.id,
@@ -80,9 +96,19 @@ async def get_project_pg_session(
ctx: ProjectContext = Depends(get_project_context),
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
) -> AsyncGenerator[AsyncSession, None]:
routing = await metadata_repo.get_project_db_routing(
ctx.project_id, DB_ROLE_BIZ_PG
)
try:
routing = await metadata_repo.get_project_db_routing(
ctx.project_id, DB_ROLE_BIZ_DATA
)
except ValueError as exc:
logger.error(
"Missing ENCRYPTION_KEY while resolving project PostgreSQL routing",
exc_info=True,
)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="ENCRYPTION_KEY is not configured for project PostgreSQL access",
) from exc
if not routing:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
@@ -98,7 +124,7 @@ async def get_project_pg_session(
pool_max_size = routing.pool_max_size or settings.PROJECT_PG_POOL_SIZE
sessionmaker = await project_connection_manager.get_pg_sessionmaker(
ctx.project_id,
DB_ROLE_BIZ_PG,
DB_ROLE_BIZ_DATA,
routing.dsn,
pool_min_size,
pool_max_size,
@@ -111,9 +137,19 @@ async def get_project_pg_connection(
ctx: ProjectContext = Depends(get_project_context),
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
) -> AsyncGenerator[AsyncConnection, None]:
routing = await metadata_repo.get_project_db_routing(
ctx.project_id, DB_ROLE_BIZ_PG
)
try:
routing = await metadata_repo.get_project_db_routing(
ctx.project_id, DB_ROLE_BIZ_DATA
)
except ValueError as exc:
logger.error(
"Missing ENCRYPTION_KEY while resolving project PostgreSQL routing",
exc_info=True,
)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="ENCRYPTION_KEY is not configured for project PostgreSQL access",
) from exc
if not routing:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
@@ -129,7 +165,7 @@ async def get_project_pg_connection(
pool_max_size = routing.pool_max_size or settings.PROJECT_PG_POOL_SIZE
pool = await project_connection_manager.get_pg_pool(
ctx.project_id,
DB_ROLE_BIZ_PG,
DB_ROLE_BIZ_DATA,
routing.dsn,
pool_min_size,
pool_max_size,
@@ -142,9 +178,19 @@ async def get_project_timescale_connection(
ctx: ProjectContext = Depends(get_project_context),
metadata_repo: MetadataRepository = Depends(get_metadata_repository),
) -> AsyncGenerator[AsyncConnection, None]:
routing = await metadata_repo.get_project_db_routing(
ctx.project_id, DB_ROLE_IOT_TS
)
try:
routing = await metadata_repo.get_project_db_routing(
ctx.project_id, DB_ROLE_IOT_DATA
)
except ValueError as exc:
logger.error(
"Missing ENCRYPTION_KEY while resolving project TimescaleDB routing",
exc_info=True,
)
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail="ENCRYPTION_KEY is not configured for project TimescaleDB access",
) from exc
if not routing:
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
@@ -160,7 +206,7 @@ async def get_project_timescale_connection(
pool_max_size = routing.pool_max_size or settings.PROJECT_TS_POOL_MAX_SIZE
pool = await project_connection_manager.get_timescale_pool(
ctx.project_id,
DB_ROLE_IOT_TS,
DB_ROLE_IOT_DATA,
routing.dsn,
pool_min_size,
pool_max_size,