大概每个开发团队都会遇到这么一天:线上接口突然慢了,用户在反馈,你打开日志平台,发现毛都没有——日志打了一堆“开始执行”“执行完毕”,耗时、中间依赖、SQL 统计一概没有。翻数据库慢日志自己拼时间线,翻前端调用链对接口,折腾一晚上最后发现是某个第三方服务超时了。
这种苦头我没少吃。所以后来团队决定把可观测性这件事认真搞起来时,我第一反应就是 OpenTelemetry + .Net 的组合。这套方案核心就四个字:轻量、开放。你不用上一套几十上百万的商业 APM,也不用在代码里埋一大堆私有 SDK 的桩,只要按 OpenTelemetry 规范把数据发出去,后端接 Prometheus、Grafana、Tempo、Loki 随便你,以后想换商业方案也能平滑迁过去。
这篇文章我不打算讲太高深的理论,就结合我实际在 .NET 8 项目里接入 OpenTelemetry 的完整过程,把核心概念、接入步骤、踩过的坑一次说清楚。适合刚准备做可观测性改造的后端开发、架构师,以及那些不想引入重型 APM、想用开源方案自己搭一套观察体系的团队。
1. 整体设计与思路拆解
1.1 为什么是 OpenTelemetry:先搞清楚三根支柱
可观测性这个词这两年特别热,但很多人把它理解成“上 APM”,这其实不对。可观测性本质上是三件事:日志(Logs)、指标(Metrics)、追踪(Traces)。OpenTelemetry 在业界被叫做“三支柱”统一标准,就是因为它把这三类数据用统一的规范和 API 串起来了。
拿 .NET 里最常见的日志场景举例。以前我们写日志用 ILogger,写完了扔到文件里或者 ELK 里,日志是日志、指标是指标、追踪是追踪,三者之间没有任何关联。接口慢了能慢多久?不知道。是哪条 SQL 慢?不知道。慢的那次调用的上下文日志在哪?得靠时间去猜。OpenTelemetry 引入了一个关键的关联维度——TraceId 和 SpanId,它们随着整个请求链路在服务之间传递,日志、指标、追踪都能靠这两个 ID 串起来。
我自己的理解是,把 OpenTelemetry 看成是一个“输送水管标准”,它不管水从哪来(日志/指标/追踪),也不管水最终到哪个水厂(后端存储),只管把管子接好,数据流通畅。这就是它的价值。
1.2 轻量级方案的具体选型:没有 Collector,全都不成立
很多 .NET 开发者第一次接触 OpenTelemetry 时有个误区:以为装上 OpenTelemetry.Exporter.Console 就算接入了。这只能算是在本地调试,离生产可用还差着十万八千里。
一个真正轻量又能落地的方案,我建议分三层来做:
- 应用层:.NET 项目里装 OpenTelemetry SDK,用 OTLP 协议把数据发出去
- 中间层:跑一个 OpenTelemetry Collector,负责接收、处理、转发数据
- 存储展示层:Prometheus 存指标,Tempo/Elasticsearch 存追踪,Grafana 统一展示,Loki 收日志
有人会问:为什么一定要加 Collector?直接把数据发到 Prometheus 不好吗?我最初也觉得多一层服务就是多一个故障点,后来实际跑了才发现这层抽象非常重要。第一,应用不需要关心后端到底用的是什么,换个存储后端只需要改 Collector 配置;第二,Collector 可以帮你在边缘做数据采样、脱敏、批处理,这些都是真实生产环境里躲不开的需求;第三,OTLP 是统一的,但 Prometheus 的 remote write、Tempo 的 HTTP 接口这些各有各的格式,Collector 天然帮你做了适配。
如果你只是本地验证,三层可以压缩成两层:应用直接 Console 导出看一眼成效。但只要是给团队用的“方案”,我建议直接从三层架构开始,后面扩展不会返工。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 核心细节解析与实操要点
2.1 .NET 里的 Traces、Metrics、Logs 到底怎么落地
这节我说点大家容易搞混的东西。很多文章一上来就贴代码,但你连 ActivitySource 和 Meter 是啥都搞不清,抄了代码也是白搭。
在 .NET 体系里,Trace(追踪) 的核心类是 ActivitySource 和 Activity。ActivitySource 相当于一个命名空间,你给它起个名字比如 MyApp.Api,然后在这个 Source 上创建 Activity,每个 Activity 就代表链路里的一个 Span。一个 Span 代表一个操作单元,比如一次 Http 请求、一次数据库查询、一段业务逻辑。Span 可以嵌套,形成一棵树,这就是 Trace。
Metrics(指标) 的核心类是 Meter,通过 Meter.CreateCounter、Meter.CreateHistogram 这类方法创建各种指标类型。比如你要统计接口调用次数,就建一个 Counter,每次请求 +1;要统计接口耗时分布,就建一个 Histogram,记录每次请求的耗时。这些指标数据会按时间序列输出,Prometheus 那种拉模式的采集器最喜欢这类数据。
Logs(日志) 在 .NET 里就是老朋友 ILogger。OpenTelemetry 核心团队后来把 ILogger 也纳入了 OTel 的范围,通过桥接把结构化日志和 TraceId 关联起来。也就是说,你在业务代码里用 logger.LogError("xxx failed") 打日志,只要 ILogger 配置了 OTel 的 Processor,这条日志会自动带上当前链路的 TraceId 和 SpanId。有了这一层关联,你在 Grafana 里点开一条 Trace,直接就能看到这链路里打出的所有日志,排查效率完全是另一个量级。
2.2 自动埋点与手动埋点怎么选
OpenTelemetry 在 .NET 里最爽的一点,就是官方已经把 ASP.NET Core 的控制器、HTTP 客户端、EF Core 的自动埋点都做好了。你只需要在注册服务时加上对应的 Instrumentation,比如 AddAspNetCoreInstrumentation()、AddHttpClientInstrumentation()、AddEntityFrameworkCoreInstrumentation(),框架就会自动给每个请求创建 Span、给每个 SQL 创建子 Span,不用你自己写埋点代码。
但自动埋点有两个致命短板:一是它只能埋“框架已知的”点,你没法给某个业务方法单独加 Span;二是它的 Span 名字是框架生成的,比如 /api/order/123,不是特别好读。
所以我的建议是:自动埋点打底,手动埋点补关键路径。自动埋点帮你把网络、数据库这些通用环节的观测数据免费拿到;手动埋点只在你真正关心的核心业务方法上加 Activity,用 [ActivitySource] 或者直接 `var activity = mySource.StartActivity
