"""HTTP 层横切关注点。 目前只有一项:统一回传 `X-Trace-ID`。接口文档把它列为所有响应的必备头, 但此前**完全没有实现**——客户端拿到错误也无法把请求与服务端日志对上。 放在独立模块而不是写在 `create_app()` 里,是为了让它可被单独测试: 中间件函数不需要起整个应用就能验证三条分支(有请求上下文 / 只有请求头 / 两者都没有)。 """ from collections.abc import Awaitable, Callable from starlette.requests import Request from starlette.responses import Response TRACE_ID_HEADER = "X-Trace-ID" RequestHandler = Callable[[Request], Awaitable[Response]] async def attach_trace_id(request: Request, call_next: RequestHandler) -> Response: """给响应附加 `X-Trace-ID`。 优先级:请求上下文里的 `trace_id`(服务端生成或从令牌解析)→ 请求头里客户端带来的 `X-Trace-ID`(透传,便于把客户端日志与服务端串起来)。两者都没有就不加头: 认证失败等场景还没建立上下文,此时凭空造一个 id 只会误导排障。 """ response = await call_next(request) context = getattr(request.state, "request_context", None) trace_id = getattr(context, "trace_id", None) or request.headers.get(TRACE_ID_HEADER) if trace_id: response.headers[TRACE_ID_HEADER] = str(trace_id) return response