activitysource 和 activitylistener 是 .net 6+ 内置的分布式追踪核心原语,必须通过 activitysource.startactivity() 创建 activity 并检查 null,监听器需注册且 shouldlistento 匹配 source 名称与版本,跨进程需手动传播 traceparent。

ActivitySource 和 ActivityListener 是 .NET 6+ 中实现 OpenTelemetry 兼容分布式追踪的核心原语,不是“可选插件”,而是 System.Diagnostics 内置的轻量级遥测基础设施。直接用它们能绕过 SDK 封装,精准控制 span 生命周期和上下文传播,但必须手动处理采样、导出、上下文注入/提取——不配 ActivitySource 的 Activity 不会触发监听器。
如何创建并启动一个可被监听的 Activity
关键点:必须通过 ActivitySource 创建,不能 new Activity;且需调用 StartActivity() 并检查返回值是否为 null(采样被拒绝时返回 null)。
常见错误是忽略采样结果,对 null 值继续调用 SetTag() 或 Stop(),导致 NullReferenceException。
-
ActivitySource实例应全局复用(如 static readonly),避免重复注册同名 source - 调用
StartActivity()前,确保已注册ActivityListener,否则 activity 不会被捕获 - span 名称建议用稳定字符串(如
"OrderService.ProcessPayment"),避免拼接动态 ID
private static readonly ActivitySource MySource = new("MyCompany.OrderService");
public void ProcessOrder(string orderId)
{
using var activity = MySource.StartActivity("ProcessOrder", ActivityKind.Server);
if (activity is null) return; // 被采样器丢弃
activity.SetTag("order.id", orderId);
activity.AddEvent(new ActivityEvent("ValidationStarted"));
try
{
// ... 执行业务逻辑
}
catch (Exception ex)
{
activity.SetStatus(ActivityStatusCode.Error, ex.Message);
activity.RecordException(ex);
throw;
}
}
如何用 ActivityListener 接收并处理跨服务的 Activity
ActivityListener 不是事件订阅器,而是生命周期钩子容器。它只响应由 ActivitySource 创建、且未被采样拒绝的 activity。监听器必须显式启用(ShouldListenTo)并设置回调(ActivityStarted / ActivityStopped)。
容易踩的坑:忘记调用 ActivityListener.Register(),或在 ShouldListenTo 中返回 false 导致完全静默。
-
ShouldListenTo接收ActivitySource实例,应比对Name和Version,而非字符串相等(版本可能为 null) -
ActivityStarted在 span 开始时触发,此时Activity.Id已生成,但ParentId可能为空(根 span) -
ActivityStopped触发时,Activity.Duration已填充,适合做耗时统计或导出
var listener = new ActivityListener
{
ShouldListenTo = src => src.Name == "MyCompany.OrderService",
ActivityStarted = activity => {
Console.WriteLine($"Started: {activity.OperationName} ({activity.Id})");
// 注入 traceparent 到 HTTP header 或消息头
},
ActivityStopped = activity => {
if (activity.Status == ActivityStatusCode.Error)
LogError(activity);
// 导出到 Jaeger/Zipkin/OTLP 等后端
}
};
ActivitySource.AddActivityListener(listener); // 必须注册!
如何传递 TraceContext 跨进程(HTTP/gRPC/消息队列)
.NET 原生不自动传播 context,必须手动注入和提取 traceparent 和 tracestate。依赖 Activity.Current?.Context 获取当前 trace ID、span ID、trace flags;用 ActivityContext.CreateImmutable() 构造传入 context。
典型错误:把 Activity.Id 当作 trace ID 使用(它是 span ID 的 W3C 格式),实际 trace ID 需从 Activity.Context.TraceId 提取。
- HTTP 客户端调用前:用
propagator.Inject()(推荐W3CActivityPropagator)写入traceparentheader - HTTP 服务端接收后:用
propagator.Extract()从 header 构造ActivityContext,再传给StartActivity(..., parentContext) - gRPC 场景下,用
Metadata替代 header;消息队列需将 trace context 序列化进 message headers 字段
// 客户端注入示例(HttpClient)
using var propagator = new W3CActivityPropagator();
var context = Activity.Current?.Context ?? default;
propagator.Inject(new PropagationContext(context, Baggage.Current), httpRequest,
(req, key, value) => req.Headers.TryAddWithoutValidation(key, value));
为什么 ActivitySource 没有触发 ActivityListener
最常见原因不是代码写错,而是执行时机或配置缺失:监听器注册晚于 activity 启动、source name 拼写大小写不一致、目标 activity 被采样器拒绝、或运行在 .NET 5 及更早版本(ActivitySource 仅 .NET 6+ 支持)。
调试建议:临时在 ShouldListenTo 里打日志,确认是否被调用;用 Activity.Current 检查当前是否有活跃 activity;用 dotnet-trace CLI 工具采集 runtime trace 验证 activity 是否真正发出。
- 确保项目 TargetFramework 是
net6.0或更高 - 不要在
ActivityListener回调中阻塞(如同步 HTTP 请求),会拖慢整个请求链路 - 若使用 OpenTelemetry .NET SDK,它内部已封装
ActivitySource和ActivityListener,此时不应再手动注册 listener,否则可能重复导出
真正难的是上下文传播的完整性——比如异步任务切换、线程池回调、Timer 回调中 Activity.Current 会丢失,必须显式传递 ActivityContext 并在新 scope 中重新 StartActivity。










