Files
root 0c511f3451 1、增加调用 AI 视频生成能力和虚拟素材库管理的对外api
2、增加后台apikkey管理
3、增加apikey单独的模型定价
4、增加apikey调用情况
5、完善所有数据的注释增加
2026-08-06 13:13:28 +08:00

188 lines
6.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""外部 API v3 日志服务。
按天分类存储在 log/api/ 目录下:
- log/api/requests/YYYY-MM-DD.log — 所有外部请求和响应
- log/api/models/YYYY-MM-DD.log — 模型调用(Volcano Ark SDK
- log/api/upscale/YYYY-MM-DD.log — 超分轮询
- log/api/errors/YYYY-MM-DD.log — 错误日志
"""
import json
import logging
import os
from datetime import datetime, timezone
# === 日志目录 ===
# video-gen-api/log/api/
_THIS_DIR = os.path.dirname(os.path.abspath(__file__))
# 上溯3级: services/api_v3 -> services -> app -> video-gen-api (即项目根目录)
_BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(_THIS_DIR)))
BASE_LOG_DIR = os.path.join(_BASE_DIR, "log", "api")
os.makedirs(BASE_LOG_DIR, exist_ok=True)
# 子目录
REQUESTS_LOG_DIR = os.path.join(BASE_LOG_DIR, "requests")
MODELS_LOG_DIR = os.path.join(BASE_LOG_DIR, "models")
UPSCALE_LOG_DIR = os.path.join(BASE_LOG_DIR, "upscale")
ERRORS_LOG_DIR = os.path.join(BASE_LOG_DIR, "errors")
for d in [REQUESTS_LOG_DIR, MODELS_LOG_DIR, UPSCALE_LOG_DIR, ERRORS_LOG_DIR]:
os.makedirs(d, exist_ok=True)
def _get_date_str() -> str:
"""获取当前日期字符串。"""
return datetime.now(timezone.utc).strftime("%Y-%m-%d")
class _DailyFileHandler(logging.Handler):
"""按天写入的日志处理器。"""
def __init__(self, log_dir: str):
super().__init__()
self.log_dir = log_dir
self._current_date = None
self._file_handler = None
self._open_file()
def _open_file(self):
"""打开当天的日志文件。"""
date_str = _get_date_str()
if date_str == self._current_date and self._file_handler:
return
if self._file_handler:
self._file_handler.close()
self._current_date = date_str
filepath = os.path.join(self.log_dir, f"{date_str}.log")
self._file_handler = logging.FileHandler(filepath, encoding="utf-8")
self._file_handler.setFormatter(
logging.Formatter("%(asctime)s | %(levelname)s | %(message)s")
)
def emit(self, record):
try:
self._open_file()
self._file_handler.emit(record)
except Exception:
self.handleError(record)
def close(self):
if self._file_handler:
self._file_handler.close()
super().close()
def _create_logger(name: str, log_dir: str) -> logging.Logger:
"""创建按天写入的 Logger。"""
logger = logging.getLogger(name)
logger.setLevel(logging.DEBUG)
# 避免重复添加 handler
if not logger.handlers:
handler = _DailyFileHandler(log_dir)
logger.addHandler(handler)
return logger
# === Logger 实例 ===
requests_logger = _create_logger("api_v3.requests", REQUESTS_LOG_DIR)
models_logger = _create_logger("api_v3.models", MODELS_LOG_DIR)
upscale_logger = _create_logger("api_v3.upscale", UPSCALE_LOG_DIR)
errors_logger = _create_logger("api_v3.errors", ERRORS_LOG_DIR)
def _safe_json(obj) -> str:
"""安全地序列化为 JSON。"""
try:
return json.dumps(obj, ensure_ascii=False, default=str)
except Exception:
return str(obj)
# === 请求/响应日志 ===
def log_request(method: str, path: str, api_key_id: str, body: dict | None = None):
"""记录外部请求。"""
requests_logger.info(
f"REQUEST | {method} {path} | key={api_key_id} | body={_safe_json(body)}"
)
def log_response(method: str, path: str, api_key_id: str, status_code: int, body=None, duration_ms: int = 0):
"""记录外部响应。"""
requests_logger.info(
f"RESPONSE | {method} {path} | key={api_key_id} | status={status_code} | duration={duration_ms}ms | body={_safe_json(body)}"
)
def log_request_error(method: str, path: str, api_key_id: str, error: str, status_code: int = 500):
"""记录请求错误。"""
errors_logger.error(
f"REQUEST_ERROR | {method} {path} | key={api_key_id} | status={status_code} | error={error}"
)
# === 模型调用日志 ===
def log_model_request(engine_id: str, model_name: str, task_id: str, params: dict):
"""记录模型调用请求。"""
models_logger.info(
f"MODEL_REQUEST | engine={engine_id} | model={model_name} | task={task_id} | params={_safe_json(params)}"
)
def log_model_response(engine_id: str, model_name: str, task_id: str, success: bool, result: dict | None = None, error: str | None = None):
"""记录模型调用响应。"""
if success:
models_logger.info(
f"MODEL_RESPONSE | engine={engine_id} | model={model_name} | task={task_id} | success | result={_safe_json(result)}"
)
else:
models_logger.error(
f"MODEL_RESPONSE | engine={engine_id} | model={model_name} | task={task_id} | failed | error={error}"
)
errors_logger.error(
f"MODEL_ERROR | engine={engine_id} | model={model_name} | task={task_id} | error={error}"
)
# === 超分轮询日志 ===
def log_upscale_poll_start(task_id: str, api_task_id: str):
"""记录超分轮询开始。"""
upscale_logger.info(f"UPSCALE_POLL_START | task={task_id} | api_task={api_task_id}")
def log_upscale_poll(task_id: str, api_task_id: str, status: str, attempt: int, result: dict | None = None):
"""记录超分轮询状态。"""
upscale_logger.info(
f"UPSCALE_POLL | task={task_id} | api_task={api_task_id} | status={status} | attempt={attempt} | result={_safe_json(result)}"
)
def log_upscale_poll_end(task_id: str, api_task_id: str, success: bool, final_status: str, total_attempts: int):
"""记录超分轮询结束。"""
if success:
upscale_logger.info(
f"UPSCALE_POLL_END | task={task_id} | api_task={api_task_id} | success | status={final_status} | attempts={total_attempts}"
)
else:
upscale_logger.error(
f"UPSCALE_POLL_END | task={task_id} | api_task={api_task_id} | failed | status={final_status} | attempts={total_attempts}"
)
errors_logger.error(
f"UPSCALE_ERROR | task={task_id} | api_task={api_task_id} | status={final_status} | attempts={total_attempts}"
)
# === 通用错误日志 ===
def log_error(category: str, message: str, details: dict | None = None):
"""记录通用错误。"""
errors_logger.error(
f"{category} | {message} | details={_safe_json(details)}"
)