137 lines
4.2 KiB
Python
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,
|
|
)
|