真人素材库本地完成
This commit is contained in:
@@ -0,0 +1,198 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
|
||||
|
||||
from app.services.log_config import LOG_DATE_FORMAT, LOG_DIR, is_enabled
|
||||
|
||||
MAX_LOG_FIELD_LENGTH = 20000
|
||||
MAX_TRACEBACK_LENGTH = 12000
|
||||
OPERATION_LOG_ROOT = os.path.join(os.path.dirname(LOG_DIR), "OperationLogs")
|
||||
SENSITIVE_KEY_PATTERNS = (
|
||||
"secret",
|
||||
"token",
|
||||
"authorization",
|
||||
"cookie",
|
||||
"credential",
|
||||
"signature",
|
||||
"accesskey",
|
||||
"access_key",
|
||||
"api_key",
|
||||
"apikey",
|
||||
"security-token",
|
||||
"x-tos-security-token",
|
||||
)
|
||||
|
||||
|
||||
def _safe_name(value: str | None, default: str = "unknown") -> str:
|
||||
text = str(value or default).strip() or default
|
||||
text = re.sub(r"[^a-zA-Z0-9_.-]+", "_", text)
|
||||
return text[:120] or default
|
||||
|
||||
|
||||
def _mask_string(value: str) -> str:
|
||||
if len(value) <= 8:
|
||||
return "***"
|
||||
return f"{value[:4]}***{value[-4:]}"
|
||||
|
||||
|
||||
def _is_sensitive_key(key: str) -> bool:
|
||||
lower = str(key).replace("-", "_").lower()
|
||||
return any(pattern in lower for pattern in SENSITIVE_KEY_PATTERNS)
|
||||
|
||||
|
||||
def _sanitize_url(value: str) -> str:
|
||||
try:
|
||||
parts = urlsplit(value)
|
||||
if not parts.scheme or not parts.netloc:
|
||||
return value
|
||||
query = []
|
||||
for k, v in parse_qsl(parts.query, keep_blank_values=True):
|
||||
query.append((k, _mask_string(v) if _is_sensitive_key(k) else v))
|
||||
return urlunsplit((parts.scheme, parts.netloc, parts.path, urlencode(query), parts.fragment))
|
||||
except Exception:
|
||||
return value
|
||||
|
||||
|
||||
def sanitize_log_value(value: Any) -> Any:
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, str):
|
||||
text = _sanitize_url(value) if value.startswith(("http://", "https://")) else value
|
||||
if len(text) > MAX_LOG_FIELD_LENGTH:
|
||||
return text[:MAX_LOG_FIELD_LENGTH] + f"...<truncated:{len(text) - MAX_LOG_FIELD_LENGTH}>"
|
||||
return text
|
||||
if isinstance(value, dict):
|
||||
output: dict[str, Any] = {}
|
||||
for k, v in value.items():
|
||||
key = str(k)
|
||||
output[key] = "***" if _is_sensitive_key(key) else sanitize_log_value(v)
|
||||
return output
|
||||
if isinstance(value, list):
|
||||
return [sanitize_log_value(v) for v in value]
|
||||
return value
|
||||
|
||||
|
||||
def build_exception_detail(exc: BaseException | None, extra: dict[str, Any] | None = None) -> dict[str, Any]:
|
||||
detail: dict[str, Any] = dict(extra or {})
|
||||
if exc is not None:
|
||||
tb = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
|
||||
if len(tb) > MAX_TRACEBACK_LENGTH:
|
||||
tb = tb[:MAX_TRACEBACK_LENGTH] + f"...<traceback_truncated:{len(tb) - MAX_TRACEBACK_LENGTH}>"
|
||||
detail.update(
|
||||
{
|
||||
"exception_type": type(exc).__name__,
|
||||
"exception_message": str(exc),
|
||||
"traceback": tb,
|
||||
}
|
||||
)
|
||||
return detail
|
||||
|
||||
|
||||
def _append_operation_log(domain: str, entry: dict[str, Any]) -> None:
|
||||
if not is_enabled():
|
||||
return
|
||||
try:
|
||||
domain_dir = os.path.join(OPERATION_LOG_ROOT, _safe_name(domain, "default"))
|
||||
os.makedirs(domain_dir, exist_ok=True)
|
||||
today = datetime.now().strftime(LOG_DATE_FORMAT)
|
||||
with open(os.path.join(domain_dir, f"{today}.log"), "a", encoding="utf-8") as f:
|
||||
f.write(json.dumps(sanitize_log_value(entry), ensure_ascii=False, default=str) + "\n")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def log_operation_event(
|
||||
*,
|
||||
domain: str,
|
||||
event_type: str,
|
||||
module: str | None = None,
|
||||
event_status: str = "success",
|
||||
source: str | None = None,
|
||||
trace_id: str | None = None,
|
||||
request_id: str | None = None,
|
||||
user_id: str | None = None,
|
||||
project_id: str | None = None,
|
||||
session_id: str | None = None,
|
||||
group_id: str | None = None,
|
||||
asset_id: str | None = None,
|
||||
task_id: str | None = None,
|
||||
step_id: str | None = None,
|
||||
remote_action: str | None = None,
|
||||
remote_request_id: str | None = None,
|
||||
message: str | None = None,
|
||||
detail: dict[str, Any] | None = None,
|
||||
error: str | None = None,
|
||||
) -> None:
|
||||
_append_operation_log(
|
||||
domain,
|
||||
{
|
||||
"timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"log_type": "operation_event",
|
||||
"domain": domain,
|
||||
"module": module or domain,
|
||||
"event_type": event_type,
|
||||
"event_status": event_status,
|
||||
"source": source,
|
||||
"trace_id": trace_id,
|
||||
"request_id": request_id,
|
||||
"user_id": user_id,
|
||||
"project_id": project_id,
|
||||
"session_id": session_id,
|
||||
"group_id": group_id,
|
||||
"asset_id": asset_id,
|
||||
"task_id": task_id,
|
||||
"step_id": step_id,
|
||||
"remote_action": remote_action,
|
||||
"remote_request_id": remote_request_id,
|
||||
"message": message,
|
||||
"detail": detail or {},
|
||||
"error": error,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def log_operation_error(*, domain: str, event_type: str, exc: BaseException | None = None, detail: dict[str, Any] | None = None, **kwargs: Any) -> None:
|
||||
kwargs.setdefault("event_status", "failed")
|
||||
kwargs["detail"] = build_exception_detail(exc, detail)
|
||||
kwargs.setdefault("error", str(exc) if exc is not None else None)
|
||||
log_operation_event(domain=domain, event_type=event_type, **kwargs)
|
||||
|
||||
|
||||
def log_remote_api_event(
|
||||
*,
|
||||
domain: str,
|
||||
remote_action: str,
|
||||
event_type: str,
|
||||
event_status: str,
|
||||
request: dict[str, Any] | None = None,
|
||||
response: dict[str, Any] | None = None,
|
||||
remote_request_id: str | None = None,
|
||||
remote_code: str | None = None,
|
||||
remote_message: str | None = None,
|
||||
**kwargs: Any,
|
||||
) -> None:
|
||||
detail = dict(kwargs.pop("detail", {}) or {})
|
||||
if request is not None:
|
||||
detail["request"] = request
|
||||
if response is not None:
|
||||
detail["response"] = response
|
||||
if remote_code is not None:
|
||||
detail["remote_code"] = remote_code
|
||||
if remote_message is not None:
|
||||
detail["remote_message"] = remote_message
|
||||
log_operation_event(
|
||||
domain=domain,
|
||||
event_type=event_type,
|
||||
event_status=event_status,
|
||||
remote_action=remote_action,
|
||||
remote_request_id=remote_request_id,
|
||||
detail=detail,
|
||||
error=remote_message if event_status == "failed" else None,
|
||||
**kwargs,
|
||||
)
|
||||
Reference in New Issue
Block a user