Go SDK采样器必须在NewTracerProvider时通过WithSampler一次性传入,初始化后不可热替换;默认为NeverSample,生产应使用ParentBased(TraceIDRatioBased(r))避免链路断裂。

Go 应用里采样策略配不生效,八成是初始化时机错了——TracerProvider 一旦创建,采样器就固化了,后续改配置、换 sampler 实例全无效。
采样器必须在 NewTracerProvider 时传入
OpenTelemetry Go SDK 不支持运行时热替换采样器。常见错误是先调用 sdktrace.NewTracerProvider(),再试图用 WithSampler 补上,这完全没用。
- 必须把
sdktrace.WithSampler(...)当作初始化参数一次性传进去 - 如果用了
otel.SetTracerProvider(tp),那tp就是最终唯一可信的实例 - 检查日志里有没有
span dropped by sampler—— 有说明采样器生效了;全没日志也不报错,大概率是漏传WithSampler - 别信“默认采样率是 1.0”,Go SDK 默认是
sdktrace.NeverSample(),真·全丢弃
生产环境必用 ParentBased 组合策略
单靠 TraceIDRatioBased(0.1) 会导致下游服务 span 全断连:A 不采样,B/C 即使想采也收不到 trace context,链路直接消失。
- 正确写法:
sdktrace.ParentBased(sdktrace.TraceIDRatioBased(0.1)) - 它会继承上游决定:上游采了,下游强制采;上游没采,再按比例判断是否采
- 避免用
span.Name()或span.Attributes()做采样判断——这些字段可能被中间件覆盖或未设置 - 若需按接口名采样(比如只采
/api/pay),得自己实现Sampler.ShouldSample,读取sc := span.SpanContext()后解析http.route属性
采样率不能硬编码,得支持运行时调整
线上流量突增或故障排查时,你没法重启服务去调高采样率。但 OpenTelemetry 本身不提供热更新接口,得自己包一层。
Go 配置库,使用 spf13/viper — 分层优先级(flag > env >file > KV > default),提供 BindPFlag/BindPFlags、SetEnvPrefix + SetEnvKeyReplace 等功能。
立即学习“go语言免费学习笔记(深入)”;
- 定义一个带
sync.RWMutex的结构体,暴露SetRate(float64) -
ShouldSample方法里只做读锁 + 复用TraceIDRatioBased内部逻辑(别每次 new 新实例) - 配合 HTTP endpoint 或信号(如
SIGHUP)触发SetRate,避免在ShouldSample里做任何 I/O 或复杂计算 - 环境变量注入采样率时,记得校验范围:
if rate > 1.0 { rate = 1.0 },否则TraceIDRatioBased(2.0)会 panic
错误日志采样别和追踪采样混用
追踪采样(Span 是否上报)和错误日志采样(log.Err() 是否打印)是两套机制,共用一个采样率反而坏事。
- 错误日志要用
zerolog.BurstSampler这类带时间窗口的策略,防毛刺:突发 5 条相同 error,之后静默 1 秒 - 指纹生成别用完整 stacktrace,用
err.Error() + filepath.Base(file) + line足够区分 - 聚合计数必须自己维护(
sync.Map存 key → count),日志库只负责输出,不负责聚合 - 别在 HTTP handler 热路径里调
log.Printf或fmt.Sprintf,I/O 和字符串分配才是真瓶颈
最常被忽略的一点:所有节点必须 NTP 时间同步,误差 >100ms 就会导致滑动窗口错位、采样统计漂移;而 time.Now().Minute() 这种写法跨小时自动复位,根本不能当窗口标识用。

















