发布于2026-07-06 阅读(0)
扫一扫,手机访问
分布式追踪这个话题,很多开发者一开始接触 System.Diagnostics.Activity 时,都会有一种“这 API 看着挺简单,用起来到处是坑”的感觉。今天就跟大家聊聊几个具体的技术细节,它们直接关系到你的 trace 能不能跑通、数据会不会丢。
先来一个最核心的判断:直接用 new Activity("name") 是跑不出有效 trace 的。重点来了——必须走 ActivitySource.StartActivity(),而且这个方法的返回值可能会是 null。注意,返回 null 不代表你的代码写错了,而是采样器在帮你“主动丢弃”。
很多人拿到 null 后第一反应是检查配置,其实这恰恰是正常情况。OpenTelemetry 默认的采样策略(比如 ParentBased)会依赖父上下文来决定是否创建 span。如果上游没有传 traceparent 头,或者当前服务根本没启用采样,那 StartActivity() 就会给它一个 null。这是系统在按规则做事,不是 BUG。
这个机制下有几个必须注意的点:
SetTag() 或 Stop(),必出 NullReferenceException。AddAlwaysOnSampler() 来验证逻辑,但上线前一定要切回合理策略。这个坑更隐蔽。你在代码里写了 new ActivitySource("user-service"),但在 TracerProviderBuilder 里只配置了 AddSource("userservice")——少了横杠。结果就是所有 activity 全部丢失,而且日志里一个字都不会报错。这才是真正让人抓狂的地方。
解决起来也很简单:建议统一用一个常量定义 source 名称,比如 public const string SourceName = "user-service";,然后在所有用到的地方直接引用。另外要提醒一下,ASP.NET Core 里的 AddAspNetCoreInstrumentation() 默认注册的 source 名是 Microsoft.AspNetCore.Hosting,不是你服务的名字——这一点很多人会搞混。
很多人在做跨服务传播时,只传了 traceparent,结果下游确实能拿到链路 ID,但像 tenant-id 这样的业务透传字段,以及供应商扩展字段,全部丢失了。这就是因为漏掉了 tracestate 和 baggage。
正确的做法是:发送方需要同时写入 traceparent、tracestate、baggage 三个头。接收方也不能仅仅用 ActivityContext.TryParse() 去解析 traceparent——还得手动重建 Baggage,用 ActivityContext.CreateBaggage(baggageHeader)。另外要注意,Baggage 本身是只读集合,要修改的话得用 WithBaggageItem("k", "v") 生成新实例。
在 async/await 场景下,Activity.Current 会自动流转,问题不大。但一旦切换到 Task.Run(() => { ... }) 或者 ThreadPool.QueueUserWorkItem,上下文就会丢失。这时候千万不要自己去弄一个 AsyncLocal 来手动存取——Activity.Current 内部已经封装好了 AsyncLocal,你直接操作它反而会破坏内部一致性。
正确的做法是:在主线程捕获 Activity.Current?.Context,然后在子线程入口用 Activity.StartActivity("name", ActivityKind.Internal, context) 显式传入。另外有个建议:细粒度的 IO 操作,比如每读一个文件块都单独开 span,是没有必要的。应该按语义操作来建,比如 "File.Load.Config",否则会把 collector 压垮。
最后想说一个很容易被忽略的事实:Activity 并不是数据沿袭的工具。它不会管变量从哪里来、被谁修改过,它只负责记录“谁触发了谁”。如果想追踪文件内容的流向,那得自己设计 DataFlowContext 加上 AsyncLocal,强行套用 Activity 只会把语义搞乱。
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
售后无忧
立即购买>office旗舰店
正版软件
正版软件
正版软件
正版软件
正版软件
1
2
3
7
8