先把当时的情况摆出来。FastAPI 里用 run_in_executor 调同步 SDK,opentelemetry-sdk 1.27 的 span 全挂在独立 trace 下,trace_id 与主请求对不上,日志里能看到两个不同的 id。

原因不止一个,得分开看。contextvars 不会自动跨线程传播。少数情况下是提交任务时没有拷贝当前上下文。

还有一种情况,手动 start_as_current_span 又新建了一条 trace。另外,中间件注入的 trace 只在请求线程里有效。

上来先在线程里打印当前 span 的 trace_id

python -c "from opentelemetry import trace;print(trace.get_current_span().get_span_context())"

提交任务前拷贝一份 contextvars

python -c "import contextvars;ctx=contextvars.copy_context();print(ctx)"

接着用 ctx.run 包住同步调用

python -c "import contextvars;ctx=contextvars.copy_context();ctx.run(print,'task')"

等前面都确认过,再验证子 span 的 parent 指向请求 span

python -c "from opentelemetry import trace;print(trace.get_current_span().parent)"

改之前先确认了最近有没有人动过配置,避免白查一遍。

这次没动代码,只在配置和运行环境上找原因。

别只看报错消失,验证要多做一步。线程内的 span 与主请求共享同一个 trace_id,父子关系在 Jaeger 里能看到完整调用链。

提醒一点。异步框架加线程池的组合最容易断链路,提交任务时带上上下文,比事后按时间戳对齐可靠。拖得越久,排查成本越高,因为现场状态会被后续操作覆盖。

作者 9823189