opentelemetry c++ sdk 异步需手动传播 context,显式获取/设置 runtimecontext;tracerprovider 和 exporter 需长生命周期;startspan 应显式传 parent;end() 须避免竞态,统一由发起方调用。

异步上下文传播必须手动绑定 opentelemetry::context::Context
OpenTelemetry C++ SDK 默认不自动穿透协程或线程切换点,std::async、boost::asio::post、folly::via 这类异步调用后,当前 span 会丢失。不是 SDK 漏了功能,而是 C++ 没有语言级 async context(不像 Go 的 goroutine 或 Python 的 asyncio.Task),必须显式传递。
实操建议:
- 在发起异步操作前,调用
opentelemetry::context::RuntimeContext::GetCurrent()拿到当前 context - 把 context 作为参数传入异步任务闭包,或封装进 task 对象里
- 在异步回调开头,用
opentelemetry::context::RuntimeContext::SetCurrent(ctx)恢复它 - 避免用全局变量或 TLS 存 context——异步任务可能跨线程,TLS 不可靠
常见错误现象:SpanProcessor::OnStart 被调用,但 span 的 parent_span_id 是 0,且 trace_id 每次都变新值。
TracerProvider 和 SpanExporter 必须生命周期长于所有异步任务
如果 TracerProvider 或其内部的 SpanExporter(比如 OtlpHttpExporter)被提前析构,后续异步回调里调用 tracer->StartSpan() 仍能返回 span,但 End() 时会静默丢弃数据——不会 crash,也不会报错,只在日志里看到 Exporter shutdown, dropping spans 这类提示。
立即学习“C++免费学习笔记(深入)”;
实操建议:
- 把
std::shared_ptr<:trace::tracerprovider></:trace::tracerprovider>声明为全局变量或 long-lived 成员变量 - 确认
OtlpHttpExporter的max_queue_size和max_export_batch_size配置合理(默认 queue 是 2048,高并发异步场景下容易满) - 不要在函数栈上构造
TracerProvider;也不要依赖 RAII 在作用域结束时释放它
性能影响:导出器队列满会导致 span 被丢弃,且无重试机制;HTTP 导出器默认同步阻塞发送,务必用 OtlpGrpcExporter 或开启 http_client 的异步模式(如 libcurl + multi-handle)。
自定义异步中间件需重载 StartSpan 的 parent 参数逻辑
标准 tracer->StartSpan("name") 默认从当前 context 查 parent span,但在中间件里(比如一个 RPC client 封装层),你往往需要把上游传来的 trace context(如 HTTP header 中的 traceparent)作为 parent,而不是当前线程 context——否则链路就断了。
实操建议:
- 解析 incoming headers 时,用
opentelemetry::trace::GetSpanContextFromW3cTraceContext提取SpanContext - 构造
opentelemetry::trace::StartSpanOptions,显式设置options.parent = extracted_context - 调用
tracer->StartSpan(name, options),而非无参版本 - 注意:若 extracted_context 无效(比如 malformed traceparent),
StartSpan仍会 fallback 到创建 root span,这符合规范但可能掩盖解析失败问题
兼容性影响:W3C Trace Context 是强制要求,别试图兼容 Zipkin 的 X-B3-TraceId —— OpenTelemetry C++ SDK v1.14+ 已移除对 B3 的原生支持,需自行解析并转换。
异步 span 结束时机容易误判:别在回调里直接 span->End()
看起来很自然的操作,其实埋了竞态风险。例如在 asio::async_read 完成回调里调用 span->End(),但此时 span 可能已被其他线程(比如超时 timer 回调)提前结束过——End() 是幂等的,但 span 内部状态已不可信,可能导致 attribute 写入失效或 duration 计算异常。
实操建议:
- 用
std::shared_ptr<:trace::span></:trace::span>管理 span 生命周期,确保 span 实例存活到所有相关异步路径完成 - 用原子 flag(如
std::atomic<bool></bool>)标记 span 是否已结束,多个回调入口先 CAS check 再 End - 更稳妥的做法:统一由发起方(比如 request handler)持有 span,在所有异步分支(成功/失败/超时)都通知它,由它做最终
End()
容易被忽略的地方:span 的 End() 不仅记录时间戳,还触发采样决策、attribute 合并、event flush;一旦错过这个时机,链路数据就残缺了——而且这种问题在线上低概率发生,很难复现。










