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

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 fastapi import APIRouter, Depends, HTTPException, status
from psycopg import AsyncConnection from psycopg import AsyncConnection
from sqlalchemy import text from sqlalchemy import text
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.project_dependencies import ( from app.auth.project_dependencies import (
@@ -20,6 +22,7 @@ from app.domain.schemas.metadata import (
from app.infra.repositories.metadata_repository import MetadataRepository from app.infra.repositories.metadata_repository import MetadataRepository
router = APIRouter() router = APIRouter()
logger = logging.getLogger(__name__)
@router.get("/meta/project", response_model=ProjectMetaResponse) @router.get("/meta/project", response_model=ProjectMetaResponse)
@@ -61,7 +64,18 @@ async def list_user_projects(
current_user=Depends(get_current_metadata_user), current_user=Depends(get_current_metadata_user),
metadata_repo: MetadataRepository = Depends(get_metadata_repository), 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 [ return [
ProjectSummaryResponse( ProjectSummaryResponse(
project_id=project.project_id, project_id=project.project_id,

View File

@@ -1,7 +1,9 @@
from dataclasses import dataclass from dataclasses import dataclass
from uuid import UUID from uuid import UUID
import logging
from fastapi import Depends, HTTPException, status from fastapi import Depends, HTTPException, status
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.keycloak_dependencies import get_current_keycloak_sub 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.db.metadata.database import get_metadata_session
from app.infra.repositories.metadata_repository import MetadataRepository from app.infra.repositories.metadata_repository import MetadataRepository
logger = logging.getLogger(__name__)
async def get_metadata_repository( async def get_metadata_repository(
session: AsyncSession = Depends(get_metadata_session), session: AsyncSession = Depends(get_metadata_session),
@@ -20,7 +24,17 @@ async def get_current_metadata_user(
keycloak_sub: UUID = Depends(get_current_keycloak_sub), keycloak_sub: UUID = Depends(get_current_keycloak_sub),
metadata_repo: MetadataRepository = Depends(get_metadata_repository), 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: if not user or not user.is_active:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user" status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user"

View File

@@ -2,8 +2,10 @@ from dataclasses import dataclass
from typing import AsyncGenerator from typing import AsyncGenerator
from uuid import UUID from uuid import UUID
import logging
from fastapi import Depends, Header, HTTPException, status from fastapi import Depends, Header, HTTPException, status
from psycopg import AsyncConnection from psycopg import AsyncConnection
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.auth.keycloak_dependencies import get_current_keycloak_sub 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.db.metadata.database import get_metadata_session
from app.infra.repositories.metadata_repository import MetadataRepository from app.infra.repositories.metadata_repository import MetadataRepository
DB_ROLE_BIZ_PG = "biz_pg" DB_ROLE_BIZ_DATA = "biz_data"
DB_ROLE_IOT_TS = "iot_ts" DB_ROLE_IOT_DATA = "iot_data"
DB_TYPE_POSTGRES = "postgresql" DB_TYPE_POSTGRES = "postgresql"
DB_TYPE_TIMESCALE = "timescaledb" DB_TYPE_TIMESCALE = "timescaledb"
logger = logging.getLogger(__name__)
@dataclass(frozen=True) @dataclass(frozen=True)
class ProjectContext: class ProjectContext:
@@ -43,31 +47,43 @@ async def get_project_context(
status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid project id" status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid project id"
) from exc ) from exc
project = await metadata_repo.get_project_by_id(project_uuid) try:
if not project: project = await metadata_repo.get_project_by_id(project_uuid)
raise HTTPException( if not project:
status_code=status.HTTP_404_NOT_FOUND, detail="Project not found" raise HTTPException(
) status_code=status.HTTP_404_NOT_FOUND, detail="Project not found"
if project.status != "active": )
raise HTTPException( if project.status != "active":
status_code=status.HTTP_403_FORBIDDEN, detail="Project is not 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) user = await metadata_repo.get_user_by_keycloak_id(keycloak_sub)
if not user: if not user:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="User not registered" status_code=status.HTTP_403_FORBIDDEN, detail="User not registered"
) )
if not user.is_active: if not user.is_active:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user" status_code=status.HTTP_403_FORBIDDEN, detail="Inactive user"
) )
membership_role = await metadata_repo.get_membership_role(project_uuid, user.id) membership_role = await metadata_repo.get_membership_role(
if not membership_role: project_uuid, user.id
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN, detail="No access to project"
) )
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( return ProjectContext(
project_id=project.id, project_id=project.id,
@@ -80,9 +96,19 @@ async def get_project_pg_session(
ctx: ProjectContext = Depends(get_project_context), ctx: ProjectContext = Depends(get_project_context),
metadata_repo: MetadataRepository = Depends(get_metadata_repository), metadata_repo: MetadataRepository = Depends(get_metadata_repository),
) -> AsyncGenerator[AsyncSession, None]: ) -> AsyncGenerator[AsyncSession, None]:
routing = await metadata_repo.get_project_db_routing( try:
ctx.project_id, DB_ROLE_BIZ_PG 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: if not routing:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, 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 pool_max_size = routing.pool_max_size or settings.PROJECT_PG_POOL_SIZE
sessionmaker = await project_connection_manager.get_pg_sessionmaker( sessionmaker = await project_connection_manager.get_pg_sessionmaker(
ctx.project_id, ctx.project_id,
DB_ROLE_BIZ_PG, DB_ROLE_BIZ_DATA,
routing.dsn, routing.dsn,
pool_min_size, pool_min_size,
pool_max_size, pool_max_size,
@@ -111,9 +137,19 @@ async def get_project_pg_connection(
ctx: ProjectContext = Depends(get_project_context), ctx: ProjectContext = Depends(get_project_context),
metadata_repo: MetadataRepository = Depends(get_metadata_repository), metadata_repo: MetadataRepository = Depends(get_metadata_repository),
) -> AsyncGenerator[AsyncConnection, None]: ) -> AsyncGenerator[AsyncConnection, None]:
routing = await metadata_repo.get_project_db_routing( try:
ctx.project_id, DB_ROLE_BIZ_PG 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: if not routing:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, 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_max_size = routing.pool_max_size or settings.PROJECT_PG_POOL_SIZE
pool = await project_connection_manager.get_pg_pool( pool = await project_connection_manager.get_pg_pool(
ctx.project_id, ctx.project_id,
DB_ROLE_BIZ_PG, DB_ROLE_BIZ_DATA,
routing.dsn, routing.dsn,
pool_min_size, pool_min_size,
pool_max_size, pool_max_size,
@@ -142,9 +178,19 @@ async def get_project_timescale_connection(
ctx: ProjectContext = Depends(get_project_context), ctx: ProjectContext = Depends(get_project_context),
metadata_repo: MetadataRepository = Depends(get_metadata_repository), metadata_repo: MetadataRepository = Depends(get_metadata_repository),
) -> AsyncGenerator[AsyncConnection, None]: ) -> AsyncGenerator[AsyncConnection, None]:
routing = await metadata_repo.get_project_db_routing( try:
ctx.project_id, DB_ROLE_IOT_TS 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: if not routing:
raise HTTPException( raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE, 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_max_size = routing.pool_max_size or settings.PROJECT_TS_POOL_MAX_SIZE
pool = await project_connection_manager.get_timescale_pool( pool = await project_connection_manager.get_timescale_pool(
ctx.project_id, ctx.project_id,
DB_ROLE_IOT_TS, DB_ROLE_IOT_DATA,
routing.dsn, routing.dsn,
pool_min_size, pool_min_size,
pool_max_size, pool_max_size,