# -*- 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, )