opentelemetry 在 c# 中实现真正可观测需补全手动埋点、指标扩展、上下文透传;activity.current 为 null 多因 instrumentation 未生效或调用绕过中间件;metrics 标签须在 createcounter 时声明;otlp 导出失败优先检查协议/端口/headers;跨服务 traceid 丢失需显式透传;resource 属性初始化后不可运行时修改。

OpenTelemetry 在 C# 中要真正“可观测”,光有自动追踪远远不够——手动埋点、指标扩展、上下文透传这三块不补全,90% 的性能瓶颈和业务异常根本查不到根因。
Activity.Current 为什么总是 null?
这不是 SDK bug,而是 instrumentation 没生效或执行路径绕过了中间件。常见于:直接 new HttpClient()、在 Program.cs 顶层代码里调用 ActivitySource.StartActivity()、或用了非 IHttpClientFactory 创建的 client。
-
AddAspNetCoreInstrumentation()只对进站请求(Controller/Minimal API)生效;出站调用必须靠AddHttpClientInstrumentation(),且 client 必须从 DI 容器解析 - 手动创建
Activity前,务必确认AddSource("xxx")中的名称和ActivitySource("xxx")完全一致(大小写敏感) - 如果在中间件外、或
WebApplication.Run()之后调用,Activity.Current必然为 null —— 此时应改用TracerProvider.Current.Tracer.StartActiveSpan()
Metrics 怎么加才不会漏掉关键业务维度?
用 Counter 记库存变更量没问题,但若只记 int,就丢掉了“谁改的”“改给哪个商品”“是否成功”这些诊断线索。OpenTelemetry Metrics 支持标签(tag),但 .NET SDK 要求你在 CreateCounter 时显式声明维度。
- 错误写法:
meter.CreateCounter<int>("stock_change")</int>→ 后续 Add 无法带属性 - 正确写法:
meter.CreateCounter<int>("stock_change", tags: new[] { "product_id", "user_id", "status" })</int> - 调用时必须按顺序传入:
_counter.Add(quantity, new("product_id", "1001"), new("user_id", "u-789"), new("status", "success")) - 注意:标签名不能含空格或特殊字符,且总数建议 ≤ 10 个,否则影响 Collector 性能
OTLP 导出失败但没报错,怎么快速定位?
日志里只看到 ExportProcessor.OnExportAsync: Export failed,没有堆栈,Wireshark 又看不到请求发出 —— 这大概率是协议/端口/headers 不匹配,而非网络不通。
- 先确认 collector 端监听的是
4317(gRPC)还是4318(HTTP/JSON);AddOtlpExporter()默认走 gRPC,若 endpoint 是http://开头,需显式设opt.Protocol = OtlpExportProtocol.HttpProtobuf - 若 collector 要求认证(如阿里云 SLS、Azure Monitor),
opt.Headers必须包含完整 key-value 对,且 key 名严格匹配后端要求(例如x-sls-otel-project不可写成project) - 本地调试时,优先用
OpenTelemetry.Exporter.Console替换 OTLP,看 trace/metric 是否生成成功 —— 能打到控制台,说明 SDK 配置正确,问题纯属导出链路
跨服务调用时 TraceId 丢失怎么办?
ASP.NET Core 自动注入 traceparent header,但 HttpClient 出站调用默认不透传自定义字段。如果你在请求头里塞了 X-Request-ID 或业务上下文(如 tenant_id),它不会自动进 span attribute。
- 要用
HttpClient的SendAsync重载,传入带Activity.Current?.Context.TraceId的HttpRequestMessage - 更稳妥的做法:注册
DelegatingHandler,在SendAsync前统一注入 span context 和业务字段:request.Headers.Add("X-Trace-ID", Activity.Current?.Context.TraceId.ToString()) - 别依赖
Activity.Current?.Baggage存业务数据 —— Baggage 默认不随 HTTP 传播,需额外配AddBaggagePropagation()扩展,且多数 collector 不解析它
最易被忽略的一点:OpenTelemetry 的 Resource 属性(比如 service.name、service.version)一旦在 SetResourceBuilder 里设置,就无法在运行时修改。哪怕你 later 调用 Activity.SetTag() 写了个同名 key,也不会覆盖 Resource —— 这导致你在 Jaeger 里看到的 service.name 和实际部署名不一致时,往往不是 endpoint 配错了,而是 Resource 初始化时就写死了错误值。










