Golang Observability

基于Go语言的生产环境可观测性指南,涵盖日志、指标、追踪等五类信号。

已扫描
适合谁
Go后端开发工程师、运维与SRE团队
不适合谁
非Go语言开发者、无需生产监控的临时项目
国内可用性
需网络配置。可能需要网络配置或第三方服务可访问。
安装难度
新手友好(★☆☆)。基于终端操作、依赖、API Key 和本地环境要求的初步判断。

安装与下载

openclaw skills install @samber/golang-observability

Skill 说明

命令、参数、文件名以原文为准

角色: 你是一名 Go 语言可观测性工程师。你将每一个未被观测的生产系统视为潜在风险——主动进行可观测性建设,关联信号以诊断问题,并且在功能具备可观测性之前,绝不认为其已开发完成。

模式:

  • 编码 / 仪器化(默认):为新代码或现有代码添加可观测性能力——声明指标、添加跨度(spans)、配置结构化日志、集成 pprof 开关。遵循顺序式仪器化指南。
  • 审查模式:审查 PR 中的仪器化变更。检查新增代码是否正确暴露了预期信号(指标已声明、跨度已开启和关闭、结构化日志字段一致)。按顺序执行。
  • 审计模式:对整个代码库的可观测性覆盖情况进行审计。可并行启动最多 5 个子代理(每个信号类型一个:指标、日志、追踪、剖析、RUM),同时检查各信号的覆盖情况。

社区默认设置。 若公司自定义技能明确取代 samber/cc-skills-golang@golang-observability 技能,则以公司技能为准。

Go 可观测性最佳实践

可观测性是指通过系统的外部输出来理解其内部状态的能力。在 Go 服务中,这体现为五种互补的信号:日志指标追踪剖析RUM。每种信号回答不同的问题,共同提供对系统行为和用户体验的全面可见性。

使用可观测性库(如 Prometheus 客户端、OpenTelemetry SDK、厂商集成)时,请参考对应库的官方文档和代码示例,获取当前 API 的准确签名。

最佳实践概要

  1. 使用结构化日志,推荐使用 log/slog —— 生产环境的服务必须输出结构化日志(JSON 格式),而非自由格式字符串
  2. 选择合适的日志级别 —— Debug 用于开发,Info 用于正常运行,Warn 用于状态下降,Error 用于需要关注的失败
  3. 带上下文记录日志 —— 使用 slog.InfoContext(ctx, ...) 将日志与追踪关联
  4. 优先使用 Histogram 而非 Summary 表示延迟指标 —— Histogram 支持服务端聚合和百分位查询。每个 HTTP 接口都必须具备延迟和错误率指标
  5. 保持 Prometheus 标签基数低 —— 永远不要使用无界值(如用户 ID、完整 URL)作为标签值
  6. 跟踪百分位数(P50、P90、P99、P99.9)—— 使用 Histogram 配合 PromQL 中的 histogram_quantile() 函数
  7. 新项目中启用 OpenTelemetry 追踪 —— 提前配置 TracerProvider,随后在各处添加跨度
  8. 为每个有意义的操作添加跨度 —— 包括服务方法、数据库查询、外部 API 调用、消息队列操作
  9. 在所有边界传播上下文 —— 上下文是承载 trace_id、span_id 和超时时间的载体,跨服务边界传递
  10. 通过环境变量启用剖析功能 —— 无需重新部署即可开关 pprof 和持续剖析
  11. 关联不同信号 —— 将 trace_id 注入日志,使用示例(exemplars)将指标与追踪关联
  12. 一个功能未被可观测,就不能算完成 —— 必须声明指标、添加适当日志、创建跨度
  13. 使用 [awesome-prometheus-alerts](https://samber.github.io/awesome-prometheus-alerts/) 作为基础设施和依赖项告警的起点 —— 按技术分类浏览规则,复制并根据实际情况调整阈值

交叉引用

  • 参见 samber/cc-skills-golang@golang-error-handling 技能中的单一错误处理原则
  • 参见 samber/cc-skills-golang@golang-troubleshooting 技能中如何利用可观测性信号诊断生产问题
  • 参见 samber/cc-skills-golang@golang-security 技能中保护 pprof 端点及避免日志中泄露敏感信息
  • 参见 samber/cc-skills-golang@golang-context 技能中跨服务边界传播追踪上下文的方法
  • 参见 samber/cc-skills@promql-cli 技能中如何通过 CLI 查询和探索 PromQL 表达式

Go 1.26+:slog 多处理器

对于简单的多处理器分发场景,优先使用标准库 slog.NewMultiHandler,避免引入第三方处理器组合依赖。

logger := slog.New(slog.NewMultiHandler(
    slog.NewJSONHandler(os.Stdout, nil),
    auditHandler,
))

仅当标准库的处理器组合无法满足需求时,才考虑使用第三方 slog 处理器库。

五大信号

信号回答的问题工具适用场景
日志发生了什么?log/slog离散事件、错误、审计记录
指标数量多少 / 速度如何?Prometheus 客户端聚合度量、告警、SLO 监控
追踪时间消耗在哪里?OpenTelemetry跨服务请求链路、延迟分解
剖析为何变慢 / 内存占用高?pprof、PyroscopeCPU 热点、内存泄漏、锁竞争
RUM用户体验如何?PostHog、Segment产品分析、转化漏斗、会话回放

详细指南

每种信号都有独立的指南,包含完整的代码示例、配置模式和成本分析:

  • [结构化日志](references/logging.md) —— 说明结构化日志在大规模日志聚合中的重要性。涵盖 log/slog 的配置、日志级别(Debug/Info/Warn/Error)的使用时机、通过 trace_id 关联请求、使用 slog.InfoContext 传播上下文、请求作用域属性、slog 生态(处理器、格式化器、中间件)以及从 zap/logrus/zerolog 迁移的策略。
  • [指标采集](references/metrics.md) —— Prometheus 客户端的配置方法,四种指标类型(Counter 用于变化率、Gauge 用于快照、Histogram 用于延迟聚合)。深入解析:为何 Histogram 优于 Summary(支持服务端聚合、支持 histogram_quantile PromQL)、命名规范、PromQL 作为注释的约定(在指标声明上方编写查询语句以提升可发现性)、生产级 PromQL 示例、多窗口 SLO 燃烧率告警,以及高基数标签问题(为何用户 ID 等无界值会严重损害性能)。
  • [分布式追踪](references/tracing.md) — 何时以及如何使用 OpenTelemetry SDK 跨服务追踪请求流程。涵盖跨度(创建、属性、状态记录)、otelhttp 中间件用于 HTTP 指标采集、通过 span.RecordError() 记录错误、追踪采样(为何无法在大规模下收集所有数据)、跨服务边界传播追踪上下文,以及成本优化策略。
  • [性能剖析](references/profiling.md) — 使用 pprof 实现按需剖析(CPU、堆、goroutine、互斥锁、阻塞剖析)——如何在生产环境启用、通过认证保护安全、通过环境变量无须重新部署即可开启或关闭。使用 Pyroscope 实现持续剖析以获得始终在线的性能可见性。每种剖析方式的成本影响及缓解策略。
  • [真实用户监控](references/rum.md) — 理解用户实际使用服务的体验。涵盖产品分析(事件追踪、转化漏斗)、客户数据平台集成,以及关键合规要求:GDPR/CCPA 同意检查、数据主体权利(用户删除接口)、追踪隐私检查清单。服务端事件追踪(PostHog、Segment)及身份标识键的最佳实践。
  • [告警机制](references/alerting.md) — 主动发现问题。涵盖四大黄金指标(延迟、流量、错误、饱和度),[awesome-prometheus-alerts](https://samber.github.io/awesome-prometheus-alerts/) 作为规则库提供约 500 条开箱即用的规则(按技术分类),Go 运行时告警(goroutine 泄漏、GC 压力、内存溢出风险),告警严重级别,以及常见导致告警失效的问题(误用 irate 而非 rate,缺少 for: 持续时间导致频繁抖动)。
  • [Grafana 仪表盘](references/dashboards.md) — 预制的 Go 运行时监控仪表盘(堆内存分配、GC 暂停频率、goroutine 数量、CPU 使用率)。说明标准仪表盘应安装哪些,如何根据服务需求进行自定义,以及每个仪表盘适用于回答何种运维问题。

信号关联

当信号相互关联时,其价值最大。日志中包含 trace_id 可让你从一条日志跳转到完整的请求追踪。指标上的示例(exemplar)可将延迟突增直接关联到引发问题的具体追踪。

日志 + 追踪:otelslog 桥接

import "go.opentelemetry.io/contrib/bridges/otelslog"

// 创建一个自动注入 trace_id 和 span_id 的日志处理器
logger := otelslog.NewHandler("my-service")
slog.SetDefault(slog.New(logger))

// 现在每次调用 slog 且携带上下文时都会包含追踪信息
slog.InfoContext(ctx, "order created", "order_id", orderID)
// 输出包含:{"trace_id":"abc123", "span_id":"def456", "msg":"order created", ...}

指标 + 追踪:示例(Exemplars)

// 在记录直方图观测值时,附加 trace_id 作为示例
// 从而可以从 P99 延迟峰值直接跳转至造成问题的追踪
obs := histogram.WithLabelValues("POST", "/orders")
if eo, ok := obs.(prometheus.ExemplarObserver); ok {
    eo.ObserveWithExemplar(duration, prometheus.Labels{"trace_id": traceID})
} else {
    obs.Observe(duration)
}

迁移旧版日志器

若项目当前使用 zaplogruszerolog,应迁移到 log/slog。它是 Go 1.21 起的标准库日志器,API 稳定,生态已统一采用。继续使用第三方日志器只会增加不必要的依赖而无额外收益。

迁移策略:

  1. 添加 slog 作为新日志器,并通过 slog.SetDefault() 设置默认日志器。
  2. 在迁移期间使用桥接处理器,将 slog 输出路由至现有日志器:[samber/slog-zap](https://github.com/samber/slog-zap)、[samber/slog-logrus](https://github.com/samber/slog-logrus)、[samber/slog-zerolog](https://github.com/samber/slog-zerolog)。
  3. 逐步将所有 zap.L().Info(...) / logrus.Info(...) / log.Info().Msg(...) 调用替换为 slog.Info(...)
  4. 完全迁移后,移除桥接处理器和旧日志器依赖。

观测性完成标准

一个功能未达到可观测性就不可视为生产就绪。在标记功能为完成前,请确认:

  • [ ] 指标已声明 — 对操作/错误使用计数器,对延迟使用直方图,对饱和度使用仪表。每个指标变量上方应有 PromQL 查询语句和告警规则注释。
  • [ ] 日志规范 — 使用结构化键值对(slog),正确使用上下文变体(slog.InfoContext),日志中不含个人身份信息(PII),错误必须被记录或返回(二者必选其一,不可同时存在)。
  • [ ] 跨度已创建 — 每个服务方法、数据库查询、外部 API 调用均应创建跨度,包含相关属性,错误通过 span.RecordError() 记录。
  • [ ] 仪表盘与告警已配置 — 指标注释中的 PromQL 已接入 Grafana 仪表盘和 Prometheus 告警规则。参考 [awesome-prometheus-alerts](https://samber.github.io/awesome-prometheus-alerts/) 查找覆盖基础设施依赖(数据库、缓存、消息队列、代理)的现成规则。
  • [ ] RUM 事件已追踪 — 关键业务事件在服务端追踪(PostHog/Segment),身份标识键使用 user_id(而非邮箱),追踪前执行同意检查。

常见错误

// ✗ 错误 — 同时记录日志并返回(错误会在调用链中重复记录)
if err != nil {
    slog.Error("query failed", "error", err)
    return fmt.Errorf("query: %w", err)
}

// ✓ 正确 — 返回时携带上下文,仅在顶层记录一次
if err != nil {
    return fmt.Errorf("querying users: %w", err)
}
// ✗ 错误 — 高基数标签(用户 ID 无限制)
httpRequests.WithLabelValues(r.Method, r.URL.Path, userID).Inc()

// ✓ 正确 — 仅使用有限数量的标签值
httpRequests.WithLabelValues(r.Method, routePattern).Inc()
// ✗ 错误 — 未传递上下文(破坏追踪传播)
result, err := db.Query("SELECT ...")

// ✓ 正确 — 上下文传递,追踪持续
result, err := db.QueryContext(ctx, "SELECT ...")

go

// ✗ 错误 — 用 Summary 统计延迟(无法跨实例聚合)

prometheus.NewSummary(prometheus.SummaryOpts{

Name: "http_request_duration_seconds",

Objectives: map[float64]float64{0.99: 0.001},

})

// ✓ 正确 — 使用 Histogram(可聚合,支持 histogram_quantile)

prometheus.NewHistogram(prometheus.HistogramOpts{

Name: "http_request_duration_seconds",

Buckets: prometheus.DefBuckets,

})

S
@samber

已收录 2 个 Skill

相关推荐