必须显式配置SetResourceBuilder设置service.name,否则trace全归到unknown_service:dotnet无法查询;AddAspNetCoreInstrumentation和AddHttpClientInstrumentation不可省略,否则HTTP入口或出站调用缺失root span导致链路断裂;OTLP导出需匹配协议(gRPC默认4317/HTTP默认4318),否则静默失败。

OpenTelemetry 在 C# .NET 里不是“装上就能用”,而是必须显式注册 ActivitySource、绑定 Instrumentation、配对 OTLP 协议——漏掉任一环,Activity.Current 就是 null,Jaeger 里就看不到你的服务。
为什么 AddOpenTelemetryTracing 必须配 SetResourceBuilder
OpenTelemetry 后端(比如 Jaeger、Tempo、OTLP Collector)靠 service.name 区分服务。不显式设置,ResourceBuilder.CreateDefault().AddService("my-api") 就不会生效,所有 trace 都归到默认名(如 unknown_service:dotnet),根本没法查。
实操要点:
-
AddService("my-api")的字符串要和你在观测平台里查服务时用的名字完全一致(区分大小写) - 别写
AddService(Environment.GetEnvironmentVariable("SERVICE_NAME"))却忘了设环境变量,会 fallback 成unknown_service - 即使用了多个
AddSource("xxx")手动埋点,service.name仍由SetResourceBuilder统一决定,不是按 source 名拆分
AddAspNetCoreInstrumentation 和 AddHttpClientInstrumentation 为什么不能少
这两个包不是“可选增强”,而是 HTTP 入口和出站调用自动埋点的硬依赖。漏掉任意一个,就会出现:Controller 方法有 span,但 HTTP 请求本身没 root span;或者 HttpClient 调用不带 traceparent header,链路在出站时断裂。
常见错误现象:
-
Activity.Current总是null—— 很大概率是漏了AddAspNetCoreInstrumentation - HTTP 出站请求没 span 或上下文丢失 —— 很大概率是漏了
AddHttpClientInstrumentation,或用了new HttpClient()而不是IHttpClientFactory创建的 client - gRPC 调用没追踪?得额外加
OpenTelemetry.Instrumentation.GrpcNetClient
OTLP 导出失败但没报错?先查 gRPC vs HTTP/JSON 协议匹配
默认 AddOtlpExporter() 走 gRPC(端口 4317),但很多本地测试用的 otel-collector 配置只开了 HTTP/JSON(端口 4318)。结果就是:日志里反复刷 ExportProcessor.OnExportAsync: Export failed,却没堆栈;Wireshark 看到客户端尝试连 :4317,而服务端只监听 :4318。
解决方式:
- 改用 HTTP/JSON 导出:
opt.Protocol = OtlpExportProtocol.HttpProtobuf,endpoint 改为http://localhost:4318/v1/traces - 确保 Collector 配置中同时启用了
otlp接收器,并区分grpc_endpoint和http_endpoint - 开发期优先用
AddConsoleExporter()验证 trace 是否生成成功,绕过网络环节
手动创建 Span 时 Activity.Current 为 null 怎么办
在非 HTTP 入口(如后台队列消费、TimerCallback、gRPC ServerStreaming)中,Activity.Current 天然为 null。直接 StartActivity() 会产生孤立 span,断开调用链。
正确做法是通过 Propagators.Extract() 从消息头还原上下文:
- RabbitMQ 场景:从
BasicDeliverEventArgs.BasicProperties.Headers提取traceparent - Kafka 场景:从
ConsumeResult.Headers解析traceparent - 必须用
ActivityContext.TryParse()+Propagators.DefaultTextMapPropagator.Extract()构造ActivityCreationOptions<T>,再传给StartActivity()
命名不一致也会导致静默丢 span:代码里 new ActivitySource("order-service"),但 AddSource("orderservice") 少了个短横,所有数据就全丢了——这种问题不会报错,只会让你在 Jaeger 里反复刷新空白页面。


















