Files
Neo-ZQYY/apps/backend/app/trace/context.py
Neo 6f8f12314f feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更:
- 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>
2026-04-06 00:03:48 +08:00

151 lines
5.3 KiB
Python
Raw 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.
# -*- 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 请求的 TraceContextrequest_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 连接的 TraceContextrequest_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 的 TraceContextrequest_id = job_ 前缀)。"""
return TraceContext(
request_id=f"job_{uuid.uuid4().hex[:12]}",
trace_type=TraceType.JOB,
path=job_name,
)