C# .NET 应用接入 OpenTelemetry 核心是三步:安装四大必需 NuGet 包、在 Program.cs 中调用 AddOpenTelemetryTracing 并配置 SetResourceBuilder 指定 service.name、正确匹配 OTLP 导出协议(gRPC 端口 4317 或 HTTP/JSON 端口 4318);任一缺失将导致 Jaeger/Tempo 无法识别服务。

直接说结论:C# .NET 应用接入 OpenTelemetry,核心就三件事——装对包、在 Program.cs 里注册 AddOpenTelemetryTracing、确保 service.name 和 OTLP endpoint 配对正确。漏掉任意一项,Jaeger 或 Tempo 里就看不到你的服务。
怎么装 OpenTelemetry NuGet 包才不踩坑
必须装这四个包,缺一不可(.NET 6+):
-
OpenTelemetry(基础运行时) -
OpenTelemetry.Exporter.OpenTelemetryProtocol(导出器,别用 Zipkin/Jaeger 原生导出器) -
OpenTelemetry.Instrumentation.AspNetCore(HTTP 入口追踪) -
OpenTelemetry.Instrumentation.Http(HttpClient 出站调用追踪)
命令行安装最稳:
dotnet add package OpenTelemetry dotnet add package OpenTelemetry.Exporter.OpenTelemetryProtocol dotnet add package OpenTelemetry.Instrumentation.AspNetCore dotnet add package OpenTelemetry.Instrumentation.Http
常见错误现象:Activity.Current 总是 null,或 HTTP 请求没 span —— 很大概率是漏了 AspNetCoreInstrumentation 或 HttpInstrumentation;或者用了 new HttpClient() 而不是 IHttpClientFactory 创建的 client。
为什么 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 名拆分
OTLP 导出失败但没报错?先查 gRPC vs HTTP/JSON 协议匹配
默认 AddOtlpExporter 走 gRPC(端口 4317),但很多本地测试用的 otel-collector 配置只开了 HTTP/JSON(端口 4318)。结果就是:日志里反复刷 ExportProcessor.OnExportAsync: Export failed,却没堆栈,Wireshark 看到客户端连的是 :4317,而服务端根本没监听这个端口。
解决方式只有两个选择,不能混用:
- 用 gRPC:endpoint 设为
http://localhost:4317,collector 配置里确认启用了otlp: grpc_endpoint: 0.0.0.0:4317 - 用 HTTP/JSON:endpoint 改成
http://localhost:4318/v1/traces,并加opt.Protocol = OtlpExportProtocol.HttpProtobuf
开发期快速验证是否生成了 trace:临时加个 .AddConsoleExporter(),看到控制台输出类似 Activity.StartActivity("GET /api/values") 就说明 tracing 已活,问题只出在导出链路上。
手动埋点时 Activity.Current 为空?得自己传上下文
在 TimerCallback、BackgroundService、RabbitMQ 消费者等非 HTTP 上下文里,Activity.Current 是 null。直接 StartActivity 会断链,生成孤立 span。
正确做法是用 Propagators.Extract 从消息头还原上下文:
- 发消息时用
propagator.Inject把 traceparent 写进 headers(如 RabbitMQ 的IBasicProperties.Headers) - 收消息时调用
Propagators.DefaultTextMapPropagator.Extract,传入 headers 字典 - 再用
ActivitySource.StartActivity(..., parentContext: extractedContext)
容易被忽略的一点:Activity 创建后无法动态添加 link 或 parent,所以“先创建再补上下文”这条路在 C# 里走不通。必须在 StartActivity 时就把 parentContext 传进去,否则链就断了。


















