golang-observability

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` 技能)。

2261星標
150分支
更新於 2026/6/6
SKILL.md
readonlyread-only
name
golang-observability
description

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 簽章。

最佳實務摘要

  1. 使用結構化日誌搭配 log/slog — 生產服務必須輸出結構化日誌(JSON),而非自由格式字串
  2. 選擇正確的日誌層級 — Debug 用於開發,Info 用於正常操作,Warn 用於降級狀態,Error 用於需要關注的失敗
  3. 使用 context 記錄日誌 — 使用 slog.InfoContext(ctx, ...) 將日誌與追蹤關聯
  4. 延遲指標優先使用 Histogram 而非 Summary — Histogram 支援伺服器端聚合和百分位查詢。每個 HTTP 端點必須有延遲和錯誤率指標。
  5. 在 Prometheus 中保持標籤基數低 — 絕不使用無界值(使用者 ID、完整 URL)作為標籤值
  6. 使用 Histogram + PromQL 中的 histogram_quantile() 追蹤百分位數(P50、P90、P99、P99.9)
  7. 在新專案中設定 OpenTelemetry 追蹤 — 盡早設定 TracerProvider,然後在各處加入 span
  8. 為每個有意義的操作加入 span — 服務方法、資料庫查詢、外部 API 呼叫、訊息佇列操作
  9. 在各處傳遞 context — context 是跨服務邊界攜帶 trace_id、span_id 和截止時間的載體
  10. 透過環境變數啟用效能分析 — 無需重新部署即可切換 pprof 和持續效能分析的開關
  11. 關聯信號 — 將 trace_id 注入日誌,使用 exemplar 將指標連結到追蹤
  12. 功能在可被觀測之前不算完成 — 宣告指標、加入適當的日誌、建立 span
  13. 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_quantile PromQL)、命名慣例、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)
}

遷移舊版日誌函式庫

如果專案目前使用 zaplogruszerolog,請遷移至 log/slog。它是自 Go 1.21 起的標準函式庫日誌器,具有穩定的 API,且生態系統已圍繞它整合。繼續使用第三方日誌器意味著維護額外的相依性而無任何好處。

遷移策略:

  1. 使用 slog.SetDefault()slog 加入為新的日誌器
  2. 在遷移期間使用橋接 handler,將 slog 輸出導向現有的日誌器:samber/slog-zapsamber/slog-logrussamber/slog-zerolog
  3. 逐步將所有 zap.L().Info(...) / logrus.Info(...) / log.Info().Msg(...) 呼叫替換為 slog.Info(...)
  4. 完全遷移後,移除橋接 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,
})