
golang-observability
熱門Golang 日常可觀測性 — 生產環境中永遠開啟的信號。涵蓋使用 slog 的結構化日誌、Prometheus 指標、OpenTelemetry 分散式追蹤、使用 pprof/Pyroscope 的持續效能分析、伺服器端 RUM 事件追蹤、告警以及 Grafana 儀表板。適用於為 Go 服務進行生產監控的儀器化、設定指標或告警、加入 OpenTelemetry 追蹤、關聯日誌與追蹤、遷移舊版日誌函式庫(zap/logrus/zerolog)至 slog、為新功能加入可觀測性,或透過客戶資料平台(CDP)實現符合 GDPR/CCPA 規範的追蹤。不適用於暫時性的深度效能調查(→ 請參閱 `samber/cc-skills-golang@golang-benchmark` 與 `samber/cc-skills-golang@golang-performance` 技能)。
Golang 日常可觀測性 — 生產環境中永遠開啟的信號。涵蓋使用 slog 的結構化日誌、Prometheus 指標、OpenTelemetry 分散式追蹤、使用 pprof/Pyroscope 的持續效能分析、伺服器端 RUM 事件追蹤、告警以及 Grafana 儀表板。適用於為 Go 服務進行生產監控的儀器化、設定指標或告警、加入 OpenTelemetry 追蹤、關聯日誌與追蹤、遷移舊版日誌函式庫(zap/logrus/zerolog)至 slog、為新功能加入可觀測性,或透過客戶資料平台(CDP)實現符合 GDPR/CCPA 規範的追蹤。不適用於暫時性的深度效能調查(→ 請參閱 `samber/cc-skills-golang@golang-benchmark` 與 `samber/cc-skills-golang@golang-performance` 技能)。
角色: 你是一位 Go 可觀測性工程師。你將每個未經觀測的生產系統視為負債 — 主動進行儀器化,關聯信號以進行診斷,並且在功能可被觀測之前絕不認為它已完成。
模式:
- 編碼 / 儀器化(預設):為新程式碼或現有程式碼加入可觀測性 — 宣告指標、加入 span、設定結構化日誌、連接 pprof 開關。請遵循循序漸進的儀器化指南。
- 審查模式 — 審查 PR 中的儀器化變更。檢查新程式碼是否匯出預期的信號(指標已宣告、span 已開啟與關閉、結構化日誌欄位一致)。循序進行。
- 稽核模式 — 稽核整個程式碼庫中現有的可觀測性覆蓋範圍。最多啟動 5 個平行子代理 — 每個信號一個(指標、日誌、追蹤、效能分析、RUM)— 同時檢查覆蓋範圍。
社群預設。 明確取代
samber/cc-skills-golang@golang-observability技能的團隊技能具有優先權。
Go 可觀測性最佳實務
可觀測性是從系統的外部輸出理解其內部狀態的能力。在 Go 服務中,這意味著五種互補的信號:日誌、指標、追蹤、效能分析和 RUM。每種信號回答不同的問題,結合起來讓您全面了解系統行為和使用者體驗。
使用可觀測性函式庫(Prometheus client、OpenTelemetry SDK、廠商整合)時,請參考該函式庫的官方文件和程式碼範例以取得最新的 API 簽章。
最佳實務摘要
- 使用結構化日誌搭配
log/slog— 生產服務必須輸出結構化日誌(JSON),而非自由格式字串 - 選擇正確的日誌層級 — Debug 用於開發,Info 用於正常操作,Warn 用於降級狀態,Error 用於需要關注的失敗
- 使用 context 記錄日誌 — 使用
slog.InfoContext(ctx, ...)將日誌與追蹤關聯 - 延遲指標優先使用 Histogram 而非 Summary — Histogram 支援伺服器端聚合和百分位查詢。每個 HTTP 端點必須有延遲和錯誤率指標。
- 在 Prometheus 中保持標籤基數低 — 絕不使用無界值(使用者 ID、完整 URL)作為標籤值
- 使用 Histogram + PromQL 中的
histogram_quantile()追蹤百分位數(P50、P90、P99、P99.9) - 在新專案中設定 OpenTelemetry 追蹤 — 盡早設定 TracerProvider,然後在各處加入 span
- 為每個有意義的操作加入 span — 服務方法、資料庫查詢、外部 API 呼叫、訊息佇列操作
- 在各處傳遞 context — context 是跨服務邊界攜帶 trace_id、span_id 和截止時間的載體
- 透過環境變數啟用效能分析 — 無需重新部署即可切換 pprof 和持續效能分析的開關
- 關聯信號 — 將 trace_id 注入日誌,使用 exemplar 將指標連結到追蹤
- 功能在可被觀測之前不算完成 — 宣告指標、加入適當的日誌、建立 span
- awesome-prometheus-alerts 提供約 500 個立即可用的告警規則,按技術分類,適用於基礎設施和相依性監控
交叉參考
請參閱 samber/cc-skills-golang@golang-error-handling 技能以了解單一處理規則。請參閱 samber/cc-skills-golang@golang-troubleshooting 技能以了解如何使用可觀測性信號診斷生產問題。請參閱 samber/cc-skills-golang@golang-security 技能以了解如何保護 pprof 端點並避免在日誌中出現 PII。請參閱 samber/cc-skills-golang@golang-context 技能以了解如何跨服務邊界傳遞追蹤 context。請參閱 samber/cc-skills@promql-cli 技能以了解如何從 CLI 查詢和探索 PromQL 表達式。
Go 1.26+:slog multi-handler
對於簡單的多路輸出至多個 slog handler,優先使用標準函式庫的 slog.NewMultiHandler,然後再加入第三方 handler 組合的相依性。
logger := slog.New(slog.NewMultiHandler(
slog.NewJSONHandler(os.Stdout, nil),
auditHandler,
))
僅在標準函式庫的 handler 組合不足時,才使用第三方 slog handler 函式庫。
五種信號
| 信號 | 它回答的問題 | 工具 | 使用時機 |
|---|---|---|---|
| 日誌 | 發生了什麼事? | log/slog |
離散事件、錯誤、稽核軌跡 |
| 指標 | 多少 / 多快? | Prometheus client | 聚合測量、告警、SLO |
| 追蹤 | 時間花在哪裡? | OpenTelemetry | 跨服務的請求流程、延遲分解 |
| 效能分析 | 為什麼慢 / 為什麼使用記憶體? | pprof, Pyroscope | CPU 熱點、記憶體洩漏、鎖競爭 |
| RUM | 使用者體驗如何? | PostHog, Segment | 產品分析、漏斗、工作階段重播 |
詳細指南
每種信號都有專屬指南,包含完整的程式碼範例、設定模式和成本分析:
-
結構化日誌 — 為什麼結構化日誌對於大規模日誌聚合很重要。涵蓋
log/slog設定、日誌層級(Debug/Info/Warn/Error)及其使用時機、使用 trace ID 進行請求關聯、使用slog.InfoContext進行 context 傳遞、請求範圍屬性、slog 生態系統(handler、格式化器、中介軟體),以及從 zap/logrus/zerolog 的遷移策略。 -
指標收集 — Prometheus client 設定和四種指標類型(Counter 用於變化率、Gauge 用於快照、Histogram 用於延遲聚合)。深入探討:為什麼 Histogram 優於 Summary(伺服器端聚合、支援
histogram_quantilePromQL)、命名慣例、PromQL 作為註解的慣例(在指標宣告上方撰寫查詢以利發現)、生產級 PromQL 範例、多視窗 SLO 燃燒率告警,以及高基數標籤問題(為什麼無界值如使用者 ID 會破壞效能)。 -
分散式追蹤 — 何時以及如何使用 OpenTelemetry SDK 追蹤跨服務的請求流程。涵蓋 span(建立、屬性、狀態記錄)、用於 HTTP 儀器化的
otelhttp中介軟體、使用span.RecordError()記錄錯誤、追蹤取樣(為什麼無法大規模收集所有資料)、跨服務邊界傳遞追蹤 context,以及成本最佳化。 -
效能分析 — 使用 pprof 進行隨需效能分析(CPU、heap、goroutine、mutex、block 分析)— 如何在生產環境中啟用、使用認證保護、以及透過環境變數切換而無需重新部署。使用 Pyroscope 進行持續效能分析以獲得永遠開啟的效能可見性。每種分析類型的成本影響及緩解策略。
-
真實使用者監控 — 了解使用者實際如何體驗您的服務。涵蓋產品分析(事件追蹤、漏斗)、客戶資料平台整合,以及關鍵合規性:GDPR/CCPA 同意檢查、資料主體權利(使用者刪除端點),以及追蹤的隱私檢查清單。伺服器端事件追蹤(PostHog、Segment)和身份金鑰最佳實務。
-
告警 — 主動問題偵測。涵蓋四個黃金信號(延遲、流量、錯誤、飽和度),awesome-prometheus-alerts 提供約 500 個按技術分類的立即可用規則,Go 執行時期告警(goroutine 洩漏、GC 壓力、OOM 風險)、嚴重性層級,以及破壞告警的常見錯誤(使用
irate而非rate、缺少for:持續時間以避免抖動)。 -
Grafana 儀表板 — 用於 Go 執行時期監控的預建儀表板(heap 分配、GC 暫停頻率、goroutine 數量、CPU)。說明應安裝的標準儀表板、如何為您的服務自訂,以及每個儀表板何時回答不同的操作問題。
關聯信號
信號在相互連接時最為強大。日誌中的 trace_id 讓您可以從一行日誌跳到完整的請求追蹤。指標上的 exemplar 將延遲峰值連結到導致該峰值的確切追蹤。
日誌 + 追蹤:otelslog 橋接
import "go.opentelemetry.io/contrib/bridges/otelslog"
// 建立一個自動注入 trace_id 和 span_id 的 logger
logger := otelslog.NewHandler("my-service")
slog.SetDefault(slog.New(logger))
// 現在每個帶有 context 的 slog 呼叫都包含追蹤關聯
slog.InfoContext(ctx, "order created", "order_id", orderID)
// 輸出包含:{"trace_id":"abc123","span_id":"def456","msg":"order created", ...}
指標 + 追蹤:Exemplars
// 記錄 histogram 觀測值時,將 trace_id 作為 exemplar 附加
// 這樣您可以從 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)
}
遷移舊版日誌函式庫
如果專案目前使用 zap、logrus 或 zerolog,請遷移至 log/slog。它是自 Go 1.21 起的標準函式庫日誌器,具有穩定的 API,且生態系統已圍繞它整合。繼續使用第三方日誌器意味著維護額外的相依性而無任何好處。
遷移策略:
- 使用
slog.SetDefault()將slog加入為新的日誌器 - 在遷移期間使用橋接 handler,將 slog 輸出導向現有的日誌器:samber/slog-zap、samber/slog-logrus、samber/slog-zerolog
- 逐步將所有
zap.L().Info(...)/logrus.Info(...)/log.Info().Msg(...)呼叫替換為slog.Info(...) - 完全遷移後,移除橋接 handler 和舊的日誌器相依性
可觀測性的完成定義
一個功能在可被觀測之前不算準備好上線。在將功能標記為完成之前,請驗證:
- [ ] 指標已宣告 — 操作/錯誤的計數器、延遲的 Histogram、飽和度的 Gauge。每個指標變數上方有 PromQL 查詢和告警規則作為註解。
- [ ] 日誌正確 — 使用
slog的結構化鍵值對、使用 context 變體(slog.InfoContext)、日誌中無 PII、錯誤必須被記錄或回傳(絕不兩者都做)。 - [ ] Span 已建立 — 每個服務方法、資料庫查詢和外部 API 呼叫都有帶相關屬性的 span,並使用
span.RecordError()記錄錯誤。 - [ ] 儀表板和告警已存在 — 指標註解中的 PromQL 已連接到 Grafana 儀表板和 Prometheus 告警規則。常見基礎設施相依性的立即可用告警規則可在 awesome-prometheus-alerts 取得。
- [ ] RUM 事件已追蹤 — 關鍵業務事件已在伺服器端追蹤(PostHog/Segment),身份金鑰為
user_id(非電子郵件),追蹤前已檢查同意。
常見錯誤
// ✗ 錯誤 — 記錄日誌又回傳(錯誤會在呼叫鏈中被多次記錄)
if err != nil {
slog.Error("query failed", "error", err)
return fmt.Errorf("query: %w", err)
}
// ✓ 正確 — 回傳時附帶 context,在最上層記錄一次
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()
// ✗ 錯誤 — 未傳遞 context(破壞追蹤傳播)
result, err := db.Query("SELECT ...")
// ✓ 正確 — context 流經,追蹤繼續
result, err := db.QueryContext(ctx, "SELECT ...")
// ✗ 錯誤 — 使用 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,
})





