Files
group_xinghuo_jinrong/app/utils/trace.py
T

75 lines
2.8 KiB
Python
Raw Normal View History

"""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
import re
from contextvars import ContextVar, Token
from uuid import uuid4
# 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}$")
_trace_id: ContextVar[str] = ContextVar("trace_id", default="")
# 独立请求级 ID(T-02 · B7 复审 P3-4):trace_id 贯通链路,request_id 标识单次
# HTTP 请求(幂等/重试对账,手册 §4.6),两者不再互用。
_request_id: ContextVar[str] = ContextVar("request_id", default="")
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
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)
def current_trace() -> str:
"""读取当前 trace_id;未初始化时返回空串(调用方应兜底生成)。"""
return _trace_id.get()
def ensure_trace() -> None:
"""无上下文时兜底归因(脚本/引擎入口),有值时保留(中间件场景不重新 set)。"""
if not current_trace():
new_trace()
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) 供中间件复位。"""
rid = request_id if HEADER_ID_PATTERN.fullmatch(request_id or "") else f"req-{uuid4().hex[:16]}"
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()