包含多个会话的累积代码变更: - backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔 - admin-web: ETL 状态页、任务管理、调度配置、登录优化 - miniprogram: 看板页面、聊天集成、UI 组件、导航更新 - etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强 - tenant-admin: 项目初始化 - db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8) - packages/shared: 枚举和工具函数更新 - tools: 数据库工具、报表生成、健康检查 - docs: PRD/架构/部署/合约文档更新 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
151 lines
5.3 KiB
Python
151 lines
5.3 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
TraceContext 与 TraceSpan 数据模型
|
||
|
||
基于 contextvars 的请求级追踪上下文,存储 request_id 和有序 span 列表。
|
||
支持 HTTP / SSE / WebSocket / 后台 Job 四种 trace 类型,
|
||
每种类型使用不同的 request_id 前缀以便区分。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import contextvars
|
||
import uuid
|
||
from dataclasses import dataclass, field
|
||
from datetime import datetime
|
||
from typing import Any
|
||
|
||
|
||
# ──────────────────────────────────────────────
|
||
# Span 类型常量(字符串常量类,不用 Enum)
|
||
# ──────────────────────────────────────────────
|
||
|
||
class SpanType:
|
||
"""所有支持的 span 类型常量。"""
|
||
|
||
HTTP_IN = "HTTP_IN"
|
||
AUTH = "AUTH"
|
||
ROUTE = "ROUTE"
|
||
SERVICE = "SERVICE"
|
||
DB_QUERY = "DB_QUERY"
|
||
DB_CONN = "DB_CONN"
|
||
DB_CONN_RELEASE = "DB_CONN_RELEASE"
|
||
HTTP_OUT = "HTTP_OUT"
|
||
ERROR = "ERROR"
|
||
DB_ERROR = "DB_ERROR"
|
||
MIDDLEWARE = "MIDDLEWARE"
|
||
MIDDLEWARE_ERROR = "MIDDLEWARE_ERROR"
|
||
SSE_START = "SSE_START"
|
||
SSE_EVENT = "SSE_EVENT"
|
||
SSE_END = "SSE_END"
|
||
AI_CALL = "AI_CALL"
|
||
AI_STREAM = "AI_STREAM"
|
||
AI_ERROR = "AI_ERROR"
|
||
WS_CONNECT = "WS_CONNECT"
|
||
WS_MESSAGE = "WS_MESSAGE"
|
||
WS_DISCONNECT = "WS_DISCONNECT"
|
||
JOB_START = "JOB_START"
|
||
JOB_END = "JOB_END"
|
||
JOB_ERROR = "JOB_ERROR"
|
||
|
||
|
||
# ──────────────────────────────────────────────
|
||
# Trace 类型常量
|
||
# ──────────────────────────────────────────────
|
||
|
||
class TraceType:
|
||
"""Trace 类型:HTTP / SSE / WebSocket / 后台 Job。"""
|
||
|
||
HTTP = "http"
|
||
SSE = "sse"
|
||
WS = "ws"
|
||
JOB = "job"
|
||
|
||
|
||
# ──────────────────────────────────────────────
|
||
# 数据模型
|
||
# ──────────────────────────────────────────────
|
||
|
||
@dataclass
|
||
class TraceSpan:
|
||
"""单个追踪节点,记录某一层的函数调用信息。"""
|
||
|
||
span_type: str # SpanType 中的常量值
|
||
module: str # 模块路径,如 "xcx_tasks"
|
||
function: str # 函数名,如 "get_task_list"
|
||
description_zh: str # 中文描述
|
||
description_en: str # 英文描述
|
||
params: dict[str, Any] # 调用参数
|
||
result_summary: str # 结果摘要
|
||
duration_ms: float # 耗时(毫秒)
|
||
timestamp: str # ISO 格式时间戳
|
||
extra: dict[str, Any] = field(default_factory=dict) # 额外信息(SQL 等)
|
||
|
||
|
||
@dataclass
|
||
class TraceContext:
|
||
"""请求级追踪上下文,维护 request_id 和有序 span 列表。"""
|
||
|
||
request_id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
|
||
trace_type: str = TraceType.HTTP # http / sse / ws / job
|
||
start_time: datetime = field(default_factory=datetime.now)
|
||
method: str = ""
|
||
path: str = ""
|
||
user_id: int | None = None
|
||
site_id: int | None = None
|
||
spans: list[TraceSpan] = field(default_factory=list)
|
||
|
||
def add_span(self, span: TraceSpan) -> None:
|
||
"""追加 span 到列表,保持插入顺序。"""
|
||
self.spans.append(span)
|
||
|
||
|
||
# ──────────────────────────────────────────────
|
||
# contextvars 存储
|
||
# ──────────────────────────────────────────────
|
||
|
||
trace_context_var: contextvars.ContextVar[TraceContext | None] = contextvars.ContextVar(
|
||
"trace_context", default=None
|
||
)
|
||
|
||
|
||
def get_current_trace() -> TraceContext | None:
|
||
"""获取当前请求的 TraceContext(无则返回 None)。"""
|
||
return trace_context_var.get()
|
||
|
||
|
||
def set_current_trace(ctx: TraceContext) -> contextvars.Token:
|
||
"""设置当前请求的 TraceContext,返回 Token 用于后续恢复。"""
|
||
return trace_context_var.set(ctx)
|
||
|
||
|
||
# ──────────────────────────────────────────────
|
||
# 工厂函数:创建不同类型的 TraceContext
|
||
# ──────────────────────────────────────────────
|
||
|
||
def create_http_trace(method: str, path: str) -> TraceContext:
|
||
"""创建 HTTP 请求的 TraceContext(request_id = uuid hex[:12])。"""
|
||
return TraceContext(
|
||
request_id=uuid.uuid4().hex[:12],
|
||
trace_type=TraceType.HTTP,
|
||
method=method,
|
||
path=path,
|
||
)
|
||
|
||
|
||
def create_ws_trace() -> TraceContext:
|
||
"""创建 WebSocket 连接的 TraceContext(request_id = ws_ 前缀)。"""
|
||
return TraceContext(
|
||
request_id=f"ws_{uuid.uuid4().hex[:12]}",
|
||
trace_type=TraceType.WS,
|
||
)
|
||
|
||
|
||
def create_job_trace(job_name: str) -> TraceContext:
|
||
"""创建后台 Job 的 TraceContext(request_id = job_ 前缀)。"""
|
||
return TraceContext(
|
||
request_id=f"job_{uuid.uuid4().hex[:12]}",
|
||
trace_type=TraceType.JOB,
|
||
path=job_name,
|
||
)
|