go-observability について
go-observabilityは、本番Goサービスを計測する開発者向けのスキルです。構造化ログ、OpenTelemetryのトレースとメトリクス、アラート、シグナル間の関連付け、テレメトリーの終了処理を解説します。
主な機能
- 01slogによる構造化ログ
- 02OpenTelemetryのトレースパターン
- 03メトリクスとカーディナリティの指針
- 04アラートとSLOの実践
- 05ログ、トレース、メトリクスの関連付け
- 06テレメトリーのグレースフル終了
活用シーン
- 01Goサービスへのオブザーバビリティ導入
- 02ログとリクエストトレースの関連付け
- 03サービスのメトリクスとアラートの設計
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ eduardo-sl/ go-agent-skills --skill go-observabilityインストール後、Claude Code で/go-observabilityを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/alerting.mdmetrics.mdslog.mdtracing.md詳細
Go のオブザーバビリティ
オブザーバビリティは、本番サービスにとって必須です。すべてのサービスで構造化ログを出力し、メトリクスを公開し、トレースコンテキストを伝搬させる必要があります。ログには標準ライブラリの log/slog を、トレースとメトリクスには OpenTelemetry を使用してください。
コミュニティのデフォルト設定です。
go-observabilityを明示的に置き換えるプロジェクトのスキルまたはエージェント設定ルールがある場合は、そちらが優先されます。
必要に応じて読み込む詳細なリファレンス資料:
references/slog.md— ハンドラーの設定、ロガーの注入、子ロガー、LogValuerによる秘匿化、トレース ID ハンドラー、ホットパス、実行時のレベル変更。references/tracing.md— トレーサープロバイダー、スパン、伝搬、サンプリング、プロファイルラベル、シャットダウン。references/metrics.md— メトリクスの定義、HTTP 計測。references/alerting.md— RED/USE、ヒストグラムとサマリーの比較、バーンレートアラート、アラートの衛生管理、エグザンプラー。
以下のセクションだけでは不十分な場合に限り、リファレンスファイルを読んでください。
1. slog による構造化ログ
本番環境では、JSON ハンドラーとともに log/slog(Go 1.21 以降)を使用してください。各行はキーと値のペアで構成し、書式付きの文章は使用しません。
// ✅ Good — structured, leveled
logger.Info("user created", slog.String("user_id", user.ID), slog.Duration("latency", elapsed))
// ❌ Bad — unparseable in production
log.Printf("user %s created in %v", user.ID, elapsed)
ロガーは依存関係として注入し、パッケージレベルのグローバルには決してアクセスしないでください。logger.With(...) で子ロガーを作成し、コンポーネント、メソッド、リクエスト ID を各呼び出し箇所で付与するのではなく、一度だけ付与してください。詳しくは references/slog.md を参照してください。
ログレベル — 一貫して使用する
| レベル | 用途 |
|---|---|
Debug | 詳細な診断情報。本番環境では無効にします |
Info | 通常の操作:リクエスト受信、ジョブ完了 |
Warn | 回復可能な問題:再試行の成功、非推奨の使用 |
Error | 対応が必要な障害:DB の停止、外部呼び出しの失敗 |
想定される状態(ユーザーが見つからない場合など)を Error レベルでログに記録してはいけません。→ Info または Warn を使用します。
機密データ — 絶対にログに記録しない
- パスワード、トークン、API キー
- クレジットカード番号全体、社会保障番号(SSN)
- 個人を特定できる個人識別情報:メールアドレス、電話番号、IP アドレス
- 個人情報(PII)を含む未加工のリクエスト本文
// ✅ Good — an opaque internal ID, no personal data
logger.Info("auth attempt", slog.String("user_id", userID), slog.Bool("success", ok))
// ❌ Bad — leaks a credential and a personal identifier
logger.Info("auth attempt", slog.String("password", password))
logger.Info("auth attempt", slog.String("user", email))
安定した内部 ID をログに記録し、個人データを保持することが許可されたシステム内で関連付けてください。ログの保持期間、検索アクセス、サードパーティのログ送信先が、ユーザーテーブルに適用される基準を満たすことはほとんどありません。
型の特性として秘匿化を実装してください。シークレットや個人データを保持する構造体に slog.LogValuer を実装し、どこで slog.Any(user) を呼び出しても、安全なフィールドのみがログに記録されるようにします。
コストと制御
- ホットパスでは
logger.LogAttrsを使用し、コストの高い属性はlogger.Enabledでガードします。 HandlerOptionsのslog.LevelVarを使うと、デプロイせずに実行時にレベルを変更できます。slog.NewMultiHandler(Go 1.26 以降)を使うと、複数のハンドラーに出力を振り分けられます。
2. OpenTelemetry による分散トレーシング
ウォーターフォールで確認する価値のあるすべての操作(受信リクエスト、DB 呼び出し、送信 HTTP、重要なビジネス処理)でスパンを開始し、必ず defer span.End() を呼び出してください。
リファレンスの例では、次のルールに従っています。
- スパン名は操作名(
GetUser、db.query)にし、完全修飾関数名やdoStuffにはしないでください。 - 失敗時には
span.RecordError(err)とspan.SetStatus(codes.Error, ...)を呼び出してください。そうしないと、失敗したリクエストのスパンが緑色で表示されます。 ctxを呼び出しチェーン全体に渡してください。チェーンの途中でcontext.Background()を使うと、気付かないうちに新しいトレースが開始され、親とのリンクが切れます。ParentBased(TraceIDRatioBased(r))でサンプリングし、サービス間で同じトレースになるようにしてください。テールサンプリングはコレクターで行い、エラーと処理の遅いリクエストを保持します。- プロファイルは第 4 のシグナルです。本番環境では継続的プロファイリングを行い、上限を設けた
pprof.Labels(ルート、テナント層)を使って、負荷の高い関数を分類します。
設定と例は references/tracing.md を参照してください。
3. OpenTelemetry/Prometheus によるメトリクス
| 種類 | 用途 | 例 |
|---|---|---|
| Counter | 単調に増加する値 | リクエスト総数、エラー総数 |
| Gauge | 増減する値 | アクティブな接続数、キューの深さ |
| Histogram | 値の分布 | リクエストのレイテンシ、レスポンスサイズ |
命名規則は <namespace>_<subsystem>_<name>_<unit> です。
http_request_duration_seconds ✅ (unit in name)
http_requests_total ✅ (counter with _total suffix)
db_connections_active ✅ (gauge, no suffix needed)
user_signups ❌ (missing _total for counter)
requestLatency ❌ (camelCase, no unit)
カーディナリティは上限を設けてください。ラベルにはルートパターン、メソッド、ステータスを使い、ユーザー ID、リクエスト ID、生の r.URL.Path は使わないでください。これらは値ごとに新しい時系列を生成し、最終的にメトリクスバックエンドを停止させます。
定義とミドルウェアについては references/metrics.md を参照してください。
4. ログ、トレース、メトリクスの連携
トレース ID のないログ行は、それを生成したリクエストと関連付けられません。ハンドラーを一度ラップして、すべての *Context ログ呼び出しで ctx から trace_id と span_id を追加します。ラッパーでは WithAttrs と WithGroup もオーバーライドする必要があります。そうしないと、子ロガーでこれらの情報が失われます。レイテンシのヒストグラムにエグザンプラー(トレース ID)を付与すると、p99 のスパイクから問題のあるトレースへ移動できます。
5. アラートと SLO
- サービスではルートごとに RED(レート、エラー、継続時間)を、プールとキューでは USE(使用率、飽和度、エラー)を測定します。
- レイテンシは必ず Histogram を使い、Summary は使わないでください。Summary の分位点はインスタンス間で集計できません。SLO のしきい値を囲むようにバケットを設定します。
- 長いウィンドウと短いウィンドウの両方で SLO の バーンレート を監視してページングし、遅いバーンはチケットを作成します。
- 原因ではなく症状に対してアラートを設定します。
irate()ではなくrate()を使い、存在すべき時系列にはabsent()を使います。すべてのページングアラートにランブックへのリンクを付けます。
クエリとエグザンプラーの設定については references/alerting.md を参照してください。
6. テレメトリのグレースフルシャットダウン
バッチスパンプロセッサはスパンをメモリに保持します。終了時に専用のタイムアウトを設定して tp.Shutdown(ctx) を呼び出してください。そうしないと、クラッシュ前の最後の数秒間、つまり重要なデータがコレクターに届きません。
references/tracing.md にある両方の例を参照してください。
検証チェックリスト
- すべてのログ出力で
fmt.Printfやlog.Printfではなく、構造化されたキーと値のペアを使うlog/slogを使用している - ロガーはグローバルとして使わず、依存関係として注入している
- ログ出力に機密データ(パスワード、トークン、PII)が含まれていない
- すべての関数呼び出しを通じて、
context.Contextによってトレースコンテキストを伝播している - 重要な処理(DB 呼び出し、HTTP リクエスト、ビジネスロジック)に対してスパンを作成している
span.RecordError(err)でスパンにエラーを記録し、エラーステータスを設定している- メトリクスが命名規則(
_seconds、_total、_bytes)に従っている - メトリクスにカーディナリティの高いラベル(ユーザー ID、リクエスト ID)が含まれていない
- サービス終了時にテレメトリプロバイダーをグレースフルにシャットダウンしている
- 関連付けのため、ログエントリにトレース ID を含めている
- レイテンシをヒストグラムとして記録し、ページングアラートをランブック付きの SLO バーンレートアラートにしている