2026-09-06 15:09:14 +08:00
|
|
|
|
"""trace_id 全链路贯通(contextvars · 架构 §5.1)。
|
|
|
|
|
|
|
|
|
|
|
|
约束(编码前必读):
|
|
|
|
|
|
- 中间件必须在 call_next 之前 set(),禁止在 endpoint/service 内重新 set();
|
|
|
|
|
|
- 后台任务(create_task)须显式 contextvars.copy_context();
|
|
|
|
|
|
- 同步 def 路由跑线程池时 context 经 anyio 传播,由 B8 trace 一致性断言实测兜底。
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
2026-09-07 02:32:03 +08:00
|
|
|
|
import re
|
2026-09-06 20:48:50 +08:00
|
|
|
|
from contextvars import ContextVar, Token
|
2026-09-06 15:09:14 +08:00
|
|
|
|
from uuid import uuid4
|
|
|
|
|
|
|
2026-09-10 18:00:20 +08:00
|
|
|
|
# HTTP 头部 ID 格式白名单(防响应头注入;不合规一律新生成)。
|
|
|
|
|
|
# **单点定义 · 三处共用**:X-Trace-Id(main 中间件)· X-Request-Id(bind_request_id)
|
|
|
|
|
|
# · client_request_id 幂等键(api/simulate,S4)。原先 main.py:44 另有一份同名副本
|
|
|
|
|
|
# (`_TRACE_ID_PATTERN`,内容与本行完全相同)→ T-9 已收敛到此处,避免白名单漂移。
|
|
|
|
|
|
HEADER_ID_PATTERN = re.compile(r"^[A-Za-z0-9._-]{1,64}$")
|
2026-09-07 02:32:03 +08:00
|
|
|
|
|
2026-09-06 15:09:14 +08:00
|
|
|
|
_trace_id: ContextVar[str] = ContextVar("trace_id", default="")
|
2026-09-07 02:32:03 +08:00
|
|
|
|
# 独立请求级 ID(T-02 · B7 复审 P3-4):trace_id 贯通链路,request_id 标识单次
|
|
|
|
|
|
# HTTP 请求(幂等/重试对账,手册 §4.6),两者不再互用。
|
|
|
|
|
|
_request_id: ContextVar[str] = ContextVar("request_id", default="")
|
2026-09-06 15:09:14 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def new_trace(trace_id: str | None = None) -> str:
|
|
|
|
|
|
"""生成并绑定新 trace_id;透传外部 X-Trace-Id 时传入该值。"""
|
|
|
|
|
|
tid = trace_id or f"trc-{uuid4().hex[:16]}"
|
|
|
|
|
|
_trace_id.set(tid)
|
|
|
|
|
|
return tid
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-06 20:48:50 +08:00
|
|
|
|
def set_trace(trace_id: str) -> Token:
|
|
|
|
|
|
"""绑定外部 trace_id(中间件入口),返回 token 供请求结束复位。"""
|
|
|
|
|
|
return _trace_id.set(trace_id)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def reset_trace(token: Token) -> None:
|
|
|
|
|
|
"""复位到中间件之前的上下文(防异步任务间串号)。"""
|
|
|
|
|
|
_trace_id.reset(token)
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-09-06 15:09:14 +08:00
|
|
|
|
def current_trace() -> str:
|
|
|
|
|
|
"""读取当前 trace_id;未初始化时返回空串(调用方应兜底生成)。"""
|
|
|
|
|
|
return _trace_id.get()
|
2026-09-06 17:40:13 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ensure_trace() -> None:
|
|
|
|
|
|
"""无上下文时兜底归因(脚本/引擎入口),有值时保留(中间件场景不重新 set)。"""
|
|
|
|
|
|
if not current_trace():
|
|
|
|
|
|
new_trace()
|
2026-09-07 02:32:03 +08:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def new_request_id() -> str:
|
|
|
|
|
|
"""生成并绑定 request_id(中间件入口每请求一次)。"""
|
|
|
|
|
|
rid = f"req-{uuid4().hex[:16]}"
|
|
|
|
|
|
_request_id.set(rid)
|
|
|
|
|
|
return rid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def bind_request_id(request_id: str | None) -> tuple[str, Token]:
|
|
|
|
|
|
"""绑定请求 request_id(合法透传否则生成);返回 (id, token) 供中间件复位。"""
|
2026-09-10 18:00:20 +08:00
|
|
|
|
rid = request_id if HEADER_ID_PATTERN.fullmatch(request_id or "") else f"req-{uuid4().hex[:16]}"
|
2026-09-07 02:32:03 +08:00
|
|
|
|
return rid, _request_id.set(rid)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def reset_request_id(token: Token) -> None:
|
|
|
|
|
|
_request_id.reset(token)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def current_request_id() -> str:
|
|
|
|
|
|
"""读取当前请求 request_id;未初始化返回空串(错误体兜底生成)。"""
|
|
|
|
|
|
return _request_id.get()
|