微服务间HTTP调用trace丢失最常见原因是未手动注入/提取uber-trace-id header;Jaeger客户端不自动透传,必须用opentracing.tracer.inject()和extract()在请求头中显式处理,否则各服务独立生成trace。

为什么直接用 jaeger-client 初始化会丢 trace?
微服务间 HTTP 调用时,trace 丢失最常见原因不是没装包,而是没透传 uber-trace-id header。Jaeger 的 Python 客户端(jaeger-client)本身不自动读取/写入请求头,必须手动注入和提取。如果你只调了 Tracer() 初始化,但没在 Flask/FastAPI 请求中间件里做 carrier 处理,那每个服务都从头生成新 trace,链路就断了。
实操建议:
- 用
opentracing.tracer.inject()在出向请求前把当前 span 注入到dictcarrier(如{'uber-trace-id': '...'}),再塞进requests.get(url, headers=...) - 进向请求时,用
opentracing.tracer.extract()从request.headers提取 carrier,再传给start_active_span(..., child_of=...) - 别依赖
jaeger-client自带的HTTPPropagator—— 它只处理底层 socket,对 Web 框架无感
FastAPI 中怎么让每个路由自动带 span?
FastAPI 没内置中间件 hook 点,靠 @app.middleware("http") 只能拿到原始 scope,没法直接访问 request.state 或挂载 span。正确做法是结合 Depends() 和生命周期管理。
实操建议:
- 定义一个依赖函数,用
tracer.start_active_span()创建入口 span,并通过request.state.span = span绑定到请求上下文 - 在依赖函数末尾用
try/finally确保span.finish()被调用,避免 span 泄漏 - 所有路由加
def route(..., span: Span = Depends(get_span)):,这样 span 就自然透传到业务逻辑里 - 注意:不要在依赖里用
async def包裹start_active_span—— Jaeger 的 tracer 不是 async-safe 的
tracer.reporter.local_agent_host_port 配置错会导致什么?
本地开发时容易填成 localhost:6831,但容器化部署后,服务在 Docker 网络里看到的 “localhost” 是自己容器内部,不是宿主机上的 Jaeger Agent。结果就是 spans 全部静默丢失,日志里也不报错。
实操建议:
- Docker Compose 场景下,Agent 服务名是
jaeger,应配local_agent_host_port=('jaeger', 6831) - Kubernetes 下用 headless service DNS 名,比如
jaeger-agent.default.svc.cluster.local - 启动时加一行日志:
print(f"Reporting to {tracer.reporter.local_agent_host_port}"),快速验证配置是否被覆盖 - 如果 Agent 不可用,
jaeger-client默认会缓存最多 100 个 spans 后丢弃,不会阻塞请求 —— 所以你可能根本意识不到它已经失效
跨线程任务(比如 Celery)怎么延续 trace?
Celery 任务默认脱离父 span 上下文,因为 Span 对象不能序列化,直接传参会报 PickleError。你不能把 span.context 当普通 dict 传,得用 Jaeger 的文本编码格式。
实操建议:
- 在发起任务前,用
tracer.extract(opentracing.Format.TEXT_MAP, carrier)提取 context,再用tracer.inject(span_context, opentracing.Format.TEXT_MAP, carrier)编码成字符串字典 - 把 carrier 字典作为
kwargs传给task.apply_async(kwargs={...}) - 任务函数内,用
tracer.extract(opentracing.Format.TEXT_MAP, kwargs.get('trace_carrier', {}))恢复 context,再start_child_span() - 别用
celery.signals.task_prerun自动注入 —— 它拿不到发起方的 span context,只能补救,不能继承
Jaeger 分布式追踪真正卡住人的地方,从来不是 SDK 怎么装,而是 header 怎么透、context 怎么串、跨进程怎么编码 —— 这些环节只要漏一环,整条 trace 就变成孤岛。尤其要注意 carrier 的生命周期和序列化边界,它比看起来更脆弱。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











