FastAPI 服务用 OpenTelemetry 埋点,主线程的请求 span 正常,提交到线程池后的 LLM 调用在 Jaeger 里成了另一条 trace,父子关系和耗时都对不上。

排查方向其实有限,先把可能性排一排。线程池没有传播 context,新线程拿不到当前 span;手动创建 tracer 时没用 start_as_current_span;异步任务脱离请求作用域,attach 之后没 detach;批量导出器队列满,部分子 span 被丢弃。

第一件事是把两边的 trace_id 打出来对比

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

随后用 contextvars 把上下文复制到线程里

python -c "import contextvars;ctx=contextvars.copy_context();print(ctx.run(lambda: 1))"

接着在子线程里用 start_as_current_span 包住调用

python -c "from opentelemetry import trace;print(trace.get_tracer('llm').start_as_current_span('chat'))"

末了确认导出器没有丢 span,再复跑一次

python -c "print({'exporter':'otlp','max_queue_size':4096})"

中间停过一次,把已经改的东西回滚干净,再重新来一遍。

改完先别撤,确认这几项。线程池里的调用与请求 span 处在同一条 trace 下,父 span 的耗时覆盖了子调用,Jaeger 里能看到完整链路。

跨线程和跨进程的链路要靠上下文显式传递,容器和线程池都不会自动帮你带,埋点前先确认这条链路上上下文有没有断。同一份配置在测试环境跑通,不代表线上没事,两边的默认值经常不一样。

作者 9823189