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