Go可观测性三支柱需组件精准匹配:日志用zerolog适配Loki字段规范,指标用CounterVec和HistogramVec语义区分打点,追踪复用全局Tracer并透传ctx确保trace_id串联。

日志该用 zerolog 还是 zap?先看你的日志流向
如果日志最终进 Loki,用 zerolog 更省心;zap 默认字段名是 lvl,而 Loki 的默认 parser 期待 level,不改配置就会丢 level 字段。
-
zerolog.New(os.Stderr).With().Timestamp().Str("service", "api").Logger()—— 初始化一次,后续所有日志自动带service和时间戳 - 若跑在 Kubernetes,务必设
zerolog.TimeFieldFormat = zerolog.TimeFormatUnix,否则时区混乱导致日志时间戳错位 - 禁止字符串拼接:
log.Info().Str("user_id", u.ID).Msg("user login")✅,log.Info().Msg("user login: " + u.ID)❌(破坏结构化)
Metrics 必须区分 Counter 和 Histogram 的语义
把请求耗时用 Counter 统计,等于放弃 P95/P99 分析能力;把成功数塞进 Histogram,等于给 Prometheus 制造一堆无用 bucket 指标。
- 请求总量、错误总数、队列长度 →
prometheus.NewCounterVec,标签用method、status_code - HTTP 延迟、DB 查询耗时 →
prometheus.NewHistogramVec,显式定义Buckets:[]float64{0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5} - 暴露前必须调用
prometheus.MustRegister(metric),否则/metrics返回空,且无任何报错提示
Tracing 不能每次请求 new Tracer,必须全局复用
新手常在 handler 里写 otel.Tracer("http"),结果每秒创建数百 tracer 实例,内存暴涨、GC 频繁、span 大量丢失。
在 Golang 中使用 samber/hot 进行内存缓存,支持 LRU、LFU、TinyLFU、W‑TinyLFU、S3FIFO、ARC、TwoQueue、SIEVE、FIFO 等淘汰算法,提供 TTL、缓存加载器及分片功能。
- 在
main()初始化一次:tracer := otel.Tracer("my-service"),然后注入到中间件或 DB 封装层 - 手动创建 span 时,必须用
ctx, span := tracer.Start(r.Context(), "db.query"),漏传ctx就断链 - 别自己实现 trace 注入逻辑:用
otelhttp.NewHandler包裹http.Handler,或直接集成ginotel.Middleware(Gin 用户)
OpenTelemetry 导出器选 OTLP 还是 Jaeger?看你的后端是否标准化
如果后端是 Tempo、Jaeger 或阿里云链路追踪(非标准 SkyWalking 封装),优先选 otlptracegrpc;如果只跑本地调试,stdouttrace 足够。
立即学习“go语言免费学习笔记(深入)”;
- 生产环境导出必须用
otlptracegrpc.WithEndpoint("collector:4317"),而非localhost(容器内 DNS 不解析) - 禁用
otlptracegrpc.WithInsecure()上线,改用 TLS + mTLS 认证 - 避免同时注册多个 exporter(如 stdout + OTLP),会干扰 span 批处理节奏,导致丢数据
trace_id 字段为空、指标 label 缺少 service、span 的 parent_span_id 为零值——这些都不是独立模块的问题,而是三者之间字段没对齐、context 没透传到底的结果。

















