Files
2026-07-09 09:10:49 +08:00

137 lines
4.2 KiB
Python

from __future__ import annotations
import logging
import traceback
from typing import Any
from sqlalchemy.ext.asyncio import AsyncSession
from app.services.operation_log_service import build_exception_detail, log_operation_event
DOMAIN = "upload_resource"
logger = logging.getLogger("videogen.upload_resource")
def _exception_payload(exc: BaseException | None, detail: dict[str, Any] | None = None) -> dict[str, Any]:
payload = dict(detail or {})
if exc is not None:
payload.setdefault("error_type", exc.__class__.__name__)
payload.setdefault("error_message", str(exc))
payload.setdefault("traceback", "".join(traceback.format_exception(type(exc), exc, exc.__traceback__)))
return payload
def log_upload_resource_event(
*,
event_type: str,
module: str | None = None,
user_id: str | None = None,
resource_id: str | None = None,
source_model: str | None = None,
source_id: str | None = None,
message: str | None = None,
detail: dict[str, Any] | None = None,
error: str | None = None,
exc: BaseException | None = None,
event_status: str = "success",
) -> None:
"""Write an upload-resource operation log.
日志失败不能影响主业务流程;失败时降级到标准 logger。
detail 只接收普通 dict,禁止传 ORM 对象,避免 commit/rollback 后懒加载异常。
"""
payload = dict(detail or {})
if resource_id:
payload["resource_id"] = resource_id
if source_model:
payload["source_model"] = source_model
if source_id:
payload["source_id"] = source_id
if exc is not None:
payload = build_exception_detail(exc, payload)
error = error or str(exc)
event_status = "failed"
try:
log_operation_event(
domain=DOMAIN,
event_type=event_type,
module=module or DOMAIN,
event_status=event_status,
source="service",
user_id=user_id,
message=message,
detail=payload,
error=error,
)
except Exception: # noqa: BLE001 - 日志降级,不能影响主流程
logger.exception(
"upload_resource operation log failed: event_type=%s user_id=%s resource_id=%s detail=%s error=%s",
event_type,
user_id,
resource_id,
payload,
error,
)
def log_upload_resource_exception(
*,
event_type: str,
message: str | None = None,
user_id: str | None = None,
resource_id: str | None = None,
resource_ids: list[str] | None = None,
module: str | None = None,
source_model: str | None = None,
source_id: str | None = None,
detail: dict[str, Any] | None = None,
exc: BaseException | None = None,
) -> None:
"""统一记录 UploadResource 相关异常。"""
payload = _exception_payload(exc, detail)
if resource_ids is not None:
payload["resource_ids"] = list(resource_ids)
log_upload_resource_event(
event_type=event_type,
module=module,
user_id=user_id,
resource_id=resource_id,
source_model=source_model,
source_id=source_id,
message=message,
detail=payload,
error=str(exc) if exc else None,
event_status="failed",
)
async def safe_rollback_with_log(
db: AsyncSession,
*,
event_type: str,
message: str | None = None,
user_id: str | None = None,
module: str | None = None,
detail: dict[str, Any] | None = None,
original_exc: BaseException | None = None,
) -> None:
"""Rollback with rollback-failure logging.
只记录 rollback 自身异常;不吞掉主异常,调用方继续 raise 原始异常。
"""
try:
await db.rollback()
except Exception as rollback_exc: # noqa: BLE001
payload = dict(detail or {})
if original_exc is not None:
payload["original_error_type"] = original_exc.__class__.__name__
payload["original_error_message"] = str(original_exc)
log_upload_resource_exception(
event_type=event_type,
message=message or "数据库回滚失败",
user_id=user_id,
module=module,
detail=payload,
exc=rollback_exc,
)