大概每个开发团队都会遇到这么一天:线上接口突然慢了,用户在反馈,你打开日志平台,发现毛都没有——日志打了一堆“开始执行”“执行完毕”,耗时、中间依赖、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("ProcessOrder") 这种方式,别搞得满屏都是埋点代码。
2.3 Resource 这个细节,决定了你能不能快速定位问题
接入 OpenTelemetry 后第一周,我最后悔的事就是没在测试环境好好配置 Resource。Resource 说白了就是你这批数据来自哪个服务的元信息,比如 service.name、service.version、deployment.environment。没有这些信息,你往 Grafana 里一看全是乱码——十几个服务的数据混在一起,根本没法按服务过滤。
有一个细节特别值得注意:service.name 的命名一定要跟你的服务发现、告警规则、日志索引保持一致。不要一个服务在 OTel 里叫 order-api,在 K8s 里叫 order-service-v1,告警规则里叫 order_svc,这样会把人逼疯的。我通常的做法是直接复用部署平台里的服务名,K8s 里就用 Deployment 的名字,纯手工部署的机器就用 Systemd service 名,确保任何地方看到的都是同一个 ID。
3. 实操过程与核心环节实现
3.1 环境准备:NuGet 包版本怎么选最稳
先说包版本问题。OpenTelemetry .NET 的包版本更新很快,而且不同包之间存在严格的依赖关系,随便升级其中一个很容易出现整个 SDK 失灵的情况。我的建议是:直接用官方 Release 的 Latest Stable 集合,不要混搭 alpha、rc 版。
下面是我在 .NET 8 项目里实际验证过的包组合,.NET 6/7 也能兼容:
xml复制<PackageReference Include="OpenTelemetry" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.Console" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Exporter.OpenTelemetryProtocol" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Extensions.Hosting" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.AspNetCore" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.Http" Version="1.9.0" />
<PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" Version="1.0.0-beta.8" />
这里有个典型的坑:OpenTelemetry.Instrumentation.EntityFrameworkCore 到现在还在 beta 阶段,你要是用 1.0.0 反而装不上。它的 API 和别的包也不太一样,用 AddEntityFrameworkCoreInstrumentation() 注册即可,但依赖的 OTel 核心版本由 NuGet 自己解析,最近几个 beta 都依赖 1.9.x,所以整体是兼容的。
如果项目还在 .NET Framework 4.8 上,情况会麻烦很多。官方 SDK 目前已经不支持 .NET Framework 了,社区方案只有 OpenTelemetry .NET 的旧版或者干脆换用 System.Diagnostics.Tracing 自己接。这种历史项目我通常建议先打通日志再说,追踪和指标后续再演进,不要指望直接套新版 SDK。
3.2 注册服务:最小可运行的配置长这样
在 Program.cs 里注册 OpenTelemetry 服务。下面这段代码是从零到一能跑通的最小配置,我加了详细注释:
csharp复制using OpenTelemetry.Metrics;
using OpenTelemetry.Resources;
using OpenTelemetry.Trace;
var builder = WebApplication.CreateBuilder(args);
// 1. 配置全局 Resource
var resourceBuilder = ResourceBuilder
.CreateDefault()
.AddService("order-api", serviceVersion: "1.0.0")
.AddEnvironmentVariableDetector(); // 自动读取 OTEL_SERVICE_NAME、OTEL_RESOURCE_ATTRIBUTES
// 2. 注册 OpenTelemetry 追踪 + 指标
builder.Services.AddOpenTelemetry()
.ConfigureResource(resource => resourceBuilder)
.WithTracing(tracing =>
{
tracing.AddSource("Order.Api"); // 手动埋点的 ActivitySource
tracing.AddAspNetCoreInstrumentation(); // 自动捕获 HTTP 请求
tracing.AddHttpClientInstrumentation(); // 自动捕获 HttpClient 调用
tracing.AddEntityFrameworkCoreInstrumentation(); // 自动捕获 EF Core SQL
tracing.AddOtlpExporter(options => options.Endpoint = new Uri("http://otel-collector:4317"));
})
.WithMetrics(metrics =>
{
metrics.AddAspNetCoreInstrumentation(); // 自动 HTTP 指标
metrics.AddRuntimeInstrumentation(); // .NET 运行时指标
metrics.AddMeter("Order.Api");
metrics.AddOtlpExporter(options => options.Endpoint = new Uri("http://otel-collector:4317"));
});
var app = builder.Build();
app.MapGet("/order/{id}", async (string id, ILogger<Program> logger) =>
{
// ActivitySource 自动从请求上下文里创建 Span,不用手动传
using var activity = OrderActivitySource.Instance.StartActivity("QueryOrder");
logger.LogInformation("开始查询订单 {OrderId}", id);
// 模拟数据库查询 + 外部调用
await Task.Delay(50);
using var http = new HttpClient();
await http.GetStringAsync("http://inventory-api/stock/123");
activity?.SetTag("order.id", id);
activity?.SetTag("order.source", "api");
return Results.Ok(new { id = id, status = "success" });
});
app.Run();
// 手动埋点的 ActivitySource
public static class OrderActivitySource
{
public const string SourceName = "Order.Api";
public static readonly System.Diagnostics.ActivitySource Instance = new(SourceName);
}
这段代码里有几个值得解释的地方。
AddEnvironmentVariableDetector() 会自动读 OTEL_SERVICE_NAME 和 OTEL_RESOURCE_ATTRIBUTES 这两个环境变量,这样你就可以在部署环境里直接通过环境变量覆盖服务名和自定义标签,不需要重新编译代码。
AddAspNetCoreInstrumentation() 不光会为每个请求创建一个 Span,还会自动收集 HTTP 路由、状态码、请求耗时这些属性。这个 Instrumentation 在 1.9.0 里已经支持 Filter、Enrich 这些配置项,你可以控制哪些请求要采集、哪些要加自定义属性,后面遇到敏感接口时可以在这里做脱敏。
手动埋点里我用了一个静态单例 ActivitySource,这是官方推荐的模式。每次 StartActivity 会从当前上下文继承 TraceId 和 ParentSpanId,这样手动埋点自动挂在请求链路上,不需要自己传 ID。
3.3 打通 Collector:OTLP 接收端配置
应用端的 NuGet 配好了,数据得有个地方收。Collector 我推荐用官方 Docker 镜像 otel/opentelemetry-collector-contrib,注意用 contrib 版本,因为多了很多第三方 exporter,比如对应 Loki、Tempo 的插件都在 contrib 里。
一个最简 Collector 配置:
yaml复制receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 5s
exporters:
debug:
verbosity: detailed
otlp/tempo:
endpoint: tempo:4317
tls:
insecure: true
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch]
exporters: [debug, otlp/tempo]
这里把 OTLP 的 gRPC 和 HTTP 端口都打开,分别对应 .NET SDK 里 AddOtlpExporter 的两种协议。默认 .NET 走 gRPC 4317,如果你想走 HTTP 4318,需要设置:
csharp复制options.Protocol = OpenTelemetry.Exporter.OtlpExportProtocol.HttpProtobuf;
debug exporter 我建议在联调阶段一直开着,这样可以在 Collector 日志里直接看到收到的 span 数量,排查“应用到底发没发出去”的问题会快很多。
3.4 指标接入 Prometheus:别漏了 Histogram 的配置
指标这块很多人以为加完 AddAspNetCoreInstrumentation 就完事了,但 Prometheus 默认是按拉模式工作的,如果你没有给指标配置一个 /metrics 端点让 Prometheus 来拉,那指标只能靠 OTel 的导出器推给 Collector,再由 Collector 转发。两种都可以,但要注意一个大坑:如果你用推模式把指标推到 Collector,Collector 再转发给 Prometheus 时,Prometheus 需要启用 remote write 接收,而 Prometheus 官方的 remote write 接收器要单独配置,不是开箱即用。
更简单的方案是:指标场景不要走 OTLP,直接用 OpenTelemetry.Exporter.Prometheus.AspNetCore 包,在 ASP.NET Core 里暴露一个 /metrics 端口,让 Prometheus 主动来拉。下面是改法:
csharp复制builder.Services.AddOpenTelemetry()
.WithMetrics(metrics =>
{
metrics.AddAspNetCoreInstrumentation();
metrics.AddRuntimeInstrumentation();
metrics.AddPrometheusExporter(); // 改为 Prometheus exporter
});
// ...
app.UseOpenTelemetryPrometheusScrapingEndpoint();
这样配置以后,Prometheus 里配置一个 scrape job:
yaml复制scrape_configs:
- job_name: 'order-api'
metrics_path: '/metrics'
static_configs:
- targets: ['order-api:9184']
一个容易被忽视的细节是:AddPrometheusExporter 默认注册的监听端口是 9464,UseOpenTelemetryPrometheusScrapingEndpoint() 会从环境变量或配置里读端口,如果没配就走 9464。你在 K8s 里要让 Service 把 9464 端口暴露出来,否则 Prometheus 抓不到。
另外,AddRuntimeInstrumentation() 会给你带来 GC 耗时、线程池数量、内存分配这些 .NET 运行时指标,这套指标对排查内存问题相当有用,建议默认开启。不过它依赖 OpenTelemetry.Instrumentation.Runtime 这个包,记得一起装。
3.5 日志接入:结构化才是灵魂
日志这块如果你是纯 .NET 应用,建议直接走 OpenTelemetry.Instrumentation.AspNetCore 之外的另一个东西:把 ILogger 桥接到 OTel。具体做法是装 OpenTelemetry.Instrumentation.Log4Net 或者 OpenTelemetry.Exporter.Console 这种已经带日志导出的包太绕了,在 .NET 8 里官方推荐的是直接配置 OpenTelemetryLoggerProvider。不过这有点麻烦,我这里给一个最简单直接的方式:还是走 OTLP,但是要装一个额外的桥接包。
csharp复制builder.Services.AddLogging(logging =>
{
logging.AddOpenTelemetry(options =>
{
options.IncludeFormattedMessage = true;
options.IncludeScopes = true;
options.ParseStateValues = true;
options.AddOtlpExporter(exporter =>
{
exporter.Endpoint = new Uri("http://otel-collector:4317");
});
});
});
这里的 IncludeFormattedMessage 很关键。如果不开,你的日志消息会变成 [{"OrderId":"123"}] 这种结构化属性,虽然字段很全,但你在 Loki 里直接搜原文就搜不到了,很不方便。开了之后,消息主体恢复成 开始查询订单 123 这种人类可读的格式,同时又带上结构化属性,两边的好处都占了。
IncludeScopes 建议也开,因为 ASP.NET Core 的管道里有很多中间件通过 BeginScope 把 TraceId 放进 LogContext,不开会把这部分关联信息丢掉。
一个常见的坑:日志的 OTLP exporter 和 Trace 的 OTLP exporter 配置是独立的,你要是只给 WithTracing 配了 OTLP,日志不会自动走 OTLP,必须单独在 AddLogging 里配一遍。我第一次接的时候就没注意,结果 Trace 在 Grafana 里能看到,日志全留在本地文件,排查半天才发现日志的 exporter 根本还没配。
3.6 验证与查询:先本地跑通再上环境
我当时在本地验证整条链路时,写了一个特别简单的模拟服务,然后开三个终端:
- 第一个终端跑 Collector,用 debug exporter 输出收到的 span
- 第二个终端跑 .NET API
- 第三个终端循环 curl
请求进来之后,Collector 的日志里应该能看到类似这样的 debug 输出:
code复制Span #0
Trace ID : f3c9d2b1a1f24e5f8a2b6c7d9e0f1a2b
Parent Span ID :
Span ID : 8f4d0a2b3c4e5f6a
Name : GET /order/{id}
Kind : Server
Start time : 2025-06-20 14:30:25.123
End time : 2025-06-20 14:30:25.176
如果你看到了输出,说明链路已经通了。如果什么都没看到,优先检查 Collector 的 endpoint 是否能通、应用配置的导出端点是否正确、网络有没有防火墙拦截。本地通了再上官网环境,问题会好排查得多。
4. 常见问题与排查技巧实录
4.1 版本不匹配导致的数据丢失
这是我进团队后帮别人排查最多的问题。很多人用 NuGet 的“更新到最新版”一键把 OpenTelemetry 全家桶升级了,结果某个包还是 beta,另一个版本比较老,整个 SDK 静默失败,数据一点都导不出来。
具体现象:应用启动没有任何报错,Console exporter 也打了日志,但后端一个 span 都收不到。
我这里整理了一个速查表,是我多次踩坑总结出来的:
| 症状 | 常见原因 | 排查方向 |
|---|---|---|
| Application 启动报 TypeLoadException | 包版本不兼容,某类型签名对不上 | 检查所有 OpenTelemetry.* 包的主版本号是否一致 |
| Console 有输出但后端收不到 | OTLP endpoint 配置错误或网络不通 | telnet/curl 测试 Collector 端口 |
| 只有 Trace 没有 Metrics | 指标 exporter 没注册或没配 endpoint | 检查 WithMetrics 里是否 AddOtlpExporter |
| 有日志但没 TraceId | 日志桥的 IncludeScopes 没开 | 确认 logging.AddOpenTelemetry 的 IncludeScopes=true |
| EF Core 没有 Span | Instrumentation.EntityFrameworkCore 包没装 | 检查 NuGet 包,并确认 AddEntityFrameworkCoreInstrumentation 已调用 |
4.2 ActivitySource 没数据:名称匹配是最大的坑
我在前面示例里给手动埋点建了一个 OrderActivitySource.Instance,如果你想采集这个 Source 的 Span,必须在 WithTracing 里调用 tracing.AddSource("Order.Api")。
名字必须完全一致,大小写敏感。很多新手在自己项目里抄了一个类名,结果 AddSource 里的字符串和 ActivitySource 构造参数不一样,或者 SourceName 写成类名而不是源名,导致手动埋点一个不上报,但自动埋点的 HTTP 请求和 SQL 又是正常的。
排查方法很简单:在应用启动时打印一下所有注册的 Source 名字,或者临时在 AddSource 里换成 tracing.AddSource("Order.Api", "Other.Source"),多个 Source 用逗号隔开。
4.3 OTLP 导出超时与队列溢出
在比较高并发的情况下,如果 Collector 响应慢,OTel SDK 的导出队列可能会积压,最坏的情况是直接丢弃数据。默认情况下 OTLP exporter 的批量调度参数是:BatchExportProcessorOptions 里的 MaxQueueSize 默认 2048,ScheduledDelayMilliseconds 默认 5000ms。
如果发现高并发下丢 span,可以调大队列和并发导出数:
csharp复制tracing.AddOtlpExporter(options =>
{
options.Endpoint = new Uri("http://otel-collector:4317");
options.BatchExportProcessorOptions = new BatchExportProcessorOptions
{
MaxQueueSize = 8192,
ScheduledDelayMilliseconds = 1000,
ExporterTimeoutMilliseconds = 10000,
MaxExportBatchSize = 4096
};
});
同时,Collector 那边也要开 batch processor,并且把 timeout 调大一点,让它攒够了再往下游发,能显著降低网络小包数量。
4.4 采样策略:不想每一条都存怎么办
数据量大的时候,完整采样所有 Trace 的成本很高。我在生产环境通常用尾采样(Tail Sampling)或者头采样(Head Sampling),但 .NET 端内置的是头采样,也就是在应用侧决定采不采样。
最简单的办法是配置采样比例:
csharp复制.WithTracing(tracing =>
{
tracing.SetSampler(new AlwaysOnSampler()); // 开发环境用
// tracing.SetSampler(new TraceIdRatioBasedSampler(0.1)); // 生产按 10% 采样
})
TraceIdRatioBasedSampler(0.1) 意味着只有 10% 的请求会采到全链路数据。注意它是基于 TraceId 的哈希值采样的,也就是说同一链路的所有 Span 会被一致地采样或丢弃,不会出现同一个 Trace 里只有一半 Span 的情况。追求低成本验证可行;但是你排查问题的时候会明显感觉到“看得到数据比看不到强”。
不过我个人建议:核心交易链路尽量用 AlwaysOnSampler,把非核心流量采样率降到 5% 以下,甚至可以在 Collector 用尾采样。尾采样可以根据规则保留有错误的 Trace、高延迟的 Trace,其他按比例丢弃,这对小团队来说性价比更高。不过尾采样配置相对复杂,下一篇我再单独写。
4.5 常见的 .NET 环境问题(顺手解决)
除了 OpenTelemetry 本身,接入时还会遇到一些 .NET 层面的项目环境问题,虽然不是 OTel 直接引入的,但项目要推进总绕不开。
- 项目里装了 Swagger(Swashbuckle),启动后直接访问根路径 404 或者空白页,这是因为 Swagger 中间件的路由和你 API 的 route 没匹配上,跟 OTel 无关,不用怀疑。
- 在 Windows 老环境上项目连服务跑不起来,报 .NET Framework 4.8 相关错误,这是目标框架版本和系统版本不匹配。.NET Framework 4.8 在旧版 Windows 上默认不带,得先装运行时再跑;这个问题和 OTel 没关系,但经常被误判。
- 用 Docker 部署 API 时遇到
error response from daemon: get https://registry-1.docker.io/v2/这类报错,说明镜像拉取超时了,多半是公司内网没配镜像加速器,配一下 daemon.json 里的 registry-mirrors 就好。
4.6 生产环境必做的三件事
第一件事:给 Collector 加优雅关闭。K8s 滚动更新时如果直接杀 Collector Pod,缓冲区里的数据会丢。在 Collector 配置里加上 grace_period: 30s,并把 Pod 的 terminationGracePeriodSeconds 设置得比它长一点。
第二件事:把 OTel SDK 的日志打开。在 .NET 里给 OTel 开自诊断日志,关键时候能救命。在 appsettings.Development.json 里加:
json复制{
"Logging": {
"OpenTelemetry": {
"LogLevel": {
"Default": "Information"
}
}
}
}
这样当 OTLP 导出失败、队列满这些异常发生时,你能从应用日志里直接看到原因。不用的时候记得关掉,不然日志量非常大。
第三件事:护住敏感数据。OpenTelemetry 是很容易把敏感信息带出去的。你给某个业务方法做了手动埋点,结果在里面 SetTag 的时候把一个身份证号放进去,Span 到了 Collector,过一阵子同事在 Grafana 上全看到了。所以团队里要约定一个原则:Span 的属性值只允许放业务 ID、状态码、耗时这些非敏感数据,PII 一律不放。真要查用户问题,用 TraceId 去日志系统里翻。
写在最后的个人体会
接入 OpenTelemetry 这几个月,我最大的感受不是“数据多了”,而是“排查问题的方式变了”。以前是打开日志文件开始猜,现在是在 Grafana 上点开一条 Trace,看它在哪个环节耗时最长,再顺着 TraceId 把日志和指标拉出来,整个链路清清楚楚。
这套改造的工作量其实不大,核心服务只花了两三天就接完了。难的不是写代码,而是把团队的习惯扭过来:日志要打结构化、核心方法要手动埋点、发布时要配置好环境变量。可一旦这些成了默认习惯,后面换存储后端、接告警、做容量规划,都会顺很多。
如果你也想在团队里推 OpenTelemetry,别贪多,先从 Trace 入手,把链路捋清楚,再逐步加上指标和日志。轻量级的价值就在这里:你不用一步到位,随时可以根据自己的需要扩展。希望这篇实战记录能帮你少踩几个坑。
