新项目默认用 zap.Logger,性能高且支持结构化日志;指标采集优先用 prometheus/client_golang,需跨链路追踪则选 OpenTelemetry;日志记录异常路径,指标专注聚合态,通过 trace_id 关联三者。

log.Logger 与 zap.Logger 怎么选
直接用标准库 log.Logger 能跑通,但线上服务扛不住高并发日志写入,尤其在频繁打点监控指标时容易阻塞主线程。zap 是目前 Go 生态最主流的结构化日志库,性能比 log 高 4–10 倍,且原生支持字段(zap.String("user_id", uid))和日志级别动态控制。
实操建议:
- 新项目默认用
zap.Logger,搭配zap.NewProduction()开箱即用;调试阶段可切zap.NewDevelopment()看彩色可读日志 - 避免全局单例裸用
zap.L(),它不带 caller 信息、无法配置采样,应显式传入或从 context 注入*zap.Logger - 若已有大量
log.Printf,别硬改——先封装一层适配器函数,逐步迁移,别碰日志格式逻辑
埋点指标该用 prometheus.Client_Go 还是 opentelemetry-go
单纯暴露 HTTP 接口供 Prometheus 拉取,用 prometheus/client_golang 足够轻量;但一旦需要跨服务链路追踪、日志/指标/链路三者关联,opentelemetry-go 是唯一合理选择。
常见错误现象:用 prometheus.NewCounterVec 统计请求量,但没加 WithLabelValues("GET", "200") 就直接 Inc(),导致 panic 报错 collected metric "http_requests_total" is not a counter。
立即学习“go语言免费学习笔记(深入)”;
实操建议:
- HTTP 服务起步阶段,优先用
prometheus.NewCounterVec+promhttp.Handler(),指标名必须符合命名规范(小写字母+下划线,如http_request_duration_seconds) - 使用
prometheus.MustRegister()前确认指标未重复注册,否则进程 panic;生产环境建议用prometheus.Register()并检查返回 error - OpenTelemetry 不要自己手写 exporter,直接用
otelhttp.NewHandler()包裹 HTTP handler,自动采集路径、状态码、延迟等基础指标
日志和指标如何避免重复打点又保持可观测性
典型陷阱:一个 HTTP 请求里既写了一条 zap.Info("request handled", zap.String("path", r.URL.Path)),又调了 reqCounter.WithLabelValues(r.Method, "200").Inc(),看起来全面,实际造成语义重叠、存储浪费,且当某一方失败时另一方可能掩盖问题。
实操建议:
- 日志只记录「异常路径」和「决策依据」:认证失败、参数校验不通过、重试三次后放弃——而不是每个 200 都记一条
- 指标专注「聚合态」:请求数、P95 延迟、错误率,不记录单次事件细节;想查具体哪次慢?靠日志里的 trace_id 关联链路系统
- 关键路径上,用
ctx = otel.TraceContext(ctx, r.Header)把 trace_id 注入 log 和 metric 上下文,确保能串起来;zap 支持zap.String("trace_id", traceID),Prometheus label 不允许存 trace_id(基数太高),别硬塞
本地开发时日志和指标怎么快速验证
本地起不来 Prometheus 或 Jaeger?别等 infra 搭建完才验证埋点逻辑。关键是把采集行为“可视化”出来,而不是依赖远端系统。
实操建议:
- 日志:启动时加
zap.AddCaller()和zap.AddStacktrace(zap.ErrorLevel),立刻看到谁调的、在哪崩的;用grep -E "(ERROR|WARN)"快速过滤 - 指标:在 HTTP handler 里加个临时 endpoint,比如
/debug/metrics,用promhttp.HandlerFor(prometheus.DefaultGatherer, promhttp.HandlerOpts{})暴露原始文本,curl 一下就能看值有没有动 - OpenTelemetry 本地验证:设环境变量
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317,再起个otel-collector并配置 logging exporter,所有 span 直接打到终端,不依赖 UI
真正难的不是加一行 logger.Info 或 counter.Inc(),而是判断这一行该不该存在、该在哪个层级注入、字段是否可被下游有效消费。指标命名冲突、日志字段类型不一致、trace_id 丢失于 goroutine 切换——这些才是上线后排查三天都找不到的点。


















