C# Activity分布式追踪上下文 C#如何手动创建和传播Activity

7次阅读

activity手动创建必须设置kind和idformat,否则跨进程传播时丢失traceid;默认构造函数创建的activity无效;需用new activity(“name”)后调用start()或setparentid();idformat须设为w3c以兼容tracecontext;服务端应从traceparent头解析而非新建;baggage和tracestate必须显式传播;跨线程需显式捕获并恢复activitycontext;httpclient应禁用自动追踪并手动注入traceparent。

C# Activity分布式追踪上下文 C#如何手动创建和传播Activity

Activity手动创建必须设置Kind和IdFormat

不设置ActivityKindIdFormat会导致后续无法正确序列化上下文,尤其在跨进程传播时会丢失TraceId。默认构造函数创建的Activity处于Stopped状态且无有效ID,不能用于分布式追踪。

  • Activity必须用new Activity("name")构造,然后立即调用SetParentId()Start()(后者自动分配Id
  • 若需手动控制ID格式(如兼容W3C TraceContext),必须设activity.IdFormat = ActivityIdFormat.W3C
  • 服务端接收请求时,应优先从HTTP头(如traceparent)解析并用Activity.ExtractRootId()还原,而非新建

手动传播ActivityContext需同步Baggage和TraceState

仅复制Activity.TraceIdActivity.SpanId不够——Baggage(业务透传键值)和TraceState(供应商扩展字段)也需显式传递,否则下游服务无法获取完整上下文。

  • 发送方:用activity.Context.ToTraceParentString() + activity.Baggage分别写入HTTP头(traceparenttracestatebaggage
  • 接收方:用ActivityContext.TryParse()解析traceparent,再用ActivityContext.CreateBaggage()重建Baggage
  • 注意Baggage是只读集合,修改需调用WithBaggageItem()生成新实例

跨线程/异步任务中Activity不会自动继承

.NET 6+ 默认启用Activity.Current在线程/Task间自动流转,但仅限于async/await路径;手动启线程(Task.RunThreadPool.QueueUserWorkItem)或同步阻塞调用会丢失上下文。

  • 显式捕获:在父上下文中调用Activity.Current?.Context保存
  • 显式恢复:在子线程入口处用Activity.StartActivity(activityName, ActivityKind.internal, context)传入原始ActivityContext
  • 避免使用AsyncLocal<activity></activity>手动管理——它已被Activity.Current底层封装,直接操作易破坏一致性

HttpClient调用时不要依赖默认Activity自动创建

HttpClient在.NET 5+中默认开启Activity自动追踪,但其行为受HttpHandler配置和DiagnosticSource订阅影响,手动传播时容易与自动逻辑冲突,导致重复Span或Context覆盖。

  • 禁用自动追踪:构造HttpClient时传入new SocketsHttpHandler { ActivityHeadersPropagated = false }
  • 手动注入:在发送前设置request.Headers.TryAddWithoutValidation("traceparent", context.ToTraceParentString())
  • 注意traceparent字符串必须严格符合W3C格式(如00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01),否则接收方解析失败

实际中最容易被忽略的是Baggage的显式传播和IdFormat的预设——这两个点一旦遗漏,跨语言服务(如Go/Java下游)就收不到预期的业务标识字段,排查时往往卡在“上下文看起来存在,但关键数据没了”。

text=ZqhQzanResources