golang-observability-opentelemetry について
OpenTelemetry、Prometheus、slogを使った、Go開発者向けの可観測性ガイドです。トレース、メトリクス、ログの相関に加え、ヘルスチェックやグレースフルシャットダウンの実装例を扱います。
主な機能
- 01OpenTelemetryによる分散トレース
- 02Prometheusのメトリクスとエンドポイント
- 03相関ID付き構造化slogログ
- 04HTTP/gRPCの計装パターン
- 05ヘルスチェックとグレースフルシャットダウン
活用シーン
- 01マイクロサービスに本番可観測性を追加
- 02サービス間のリクエスト追跡と性能問題の調査
- 03ログ、トレース、メトリクスを関連付けたデバッグ
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ bobmatnyc/ claude-mpm-skills --skill golang-observability-opentelemetryインストール後、Claude Code で/golang-observability-opentelemetryを入力して読み込まれたか確認します。
同梱ファイルmetadata.json
詳細
OpenTelemetry を使用した Go のオブザーバビリティ
概要
現代の Go アプリケーションでは、トレース、メトリクス、ログという 3 つの柱を通じた包括的なオブザーバビリティが求められます。OpenTelemetry はベンダーに依存しない分散トレーシングの計装を提供し、Prometheus は強力なメトリクス収集機能を備え、Go の slog パッケージ(1.21 以降)はオーバーヘッドを最小限に抑えた構造化ログを実現します。
主な機能:
- 🔍 OpenTelemetry:コンテキスト伝播を備えた分散トレーシング
- 📊 Prometheus:/metrics エンドポイントを使用したメトリクス収集
- 📝 構造化ログ:JSON 形式と相関 ID に対応した slog
- 🎯 自動計装:HTTP/gRPC ミドルウェアのパターン
- 💚 ヘルスチェック:Kubernetes 対応のレディネス/ライブネスプローブ
- 🔄 グレースフルシャットダウン:エクスポーターの正常なシャットダウンとシグナル処理
このスキルを使用する場面
以下の場合にこのスキルを有効にしてください:
- 本番環境のオブザーバビリティのためにマイクロサービスを計装する
- サービス境界を越えた分散トレーシングを設定する
- Prometheus/Grafana を使用した運用ダッシュボードを作成する
- 本番環境のパフォーマンス問題やボトルネックをデバッグする
- SLO を実装し、SLI を監視する
- 既存の Go アプリケーションにオブザーバビリティを追加する
- デバッグのためにログ、トレース、メトリクスを関連付ける
オブザーバビリティの基本原則
3 つの柱
- トレース:分散システム全体にわたるリクエストの流れを把握する
- メトリクス:システムの動作とパフォーマンスを経時的に測定する
- ログ:デバッグや監査のために個々のイベントを記録する
相関戦略
3 つの柱すべてで共通の識別子を共有する必要があります:
- トレース ID:1 つのリクエスト内のすべての操作を関連付ける
- スパン ID:トレース内の特定の操作を識別する
- リクエスト ID:ログをトレースおよびメトリクスと関連付ける
OpenTelemetry の統合
インストール
go get go.opentelemetry.io/otel
go get go.opentelemetry.io/otel/sdk
go get go.opentelemetry.io/otel/exporters/jaeger
go get go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
基本設定
package main
import (
"context"
"log"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/jaeger"
"go.opentelemetry.io/otel/sdk/resource"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
semconv "go.opentelemetry.io/otel/semconv/v1.21.0"
)
func initTracer(serviceName string) (*sdktrace.TracerProvider, error) {
// Create Jaeger exporter
exporter, err := jaeger.New(jaeger.WithCollectorEndpoint(
jaeger.WithEndpoint("http://localhost:14268/api/traces"),
))
if err != nil {
return nil, err
}
// Create resource with service name
res, err := resource.Merge(
resource.Default(),
resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceName(serviceName),
semconv.ServiceVersion("1.0.0"),
),
)
if err != nil {
return nil, err
}
// Create tracer provider
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(res),
sdktrace.WithSampler(sdktrace.AlwaysSample()), // Use probability sampler in production
)
otel.SetTracerProvider(tp)
return tp, nil
}
func main() {
tp, err := initTracer("order-service")
if err != nil {
log.Fatal(err)
}
defer func() {
if err := tp.Shutdown(context.Background()); err != nil {
log.Printf("Error shutting down tracer: %v", err)
}
}()
// Application code...
}
スパンの作成
import (
"context"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
"go.opentelemetry.io/otel/codes"
"go.opentelemetry.io/otel/trace"
)
func ProcessOrder(ctx context.Context, order Order) error {
tracer := otel.Tracer("order-service")
ctx, span := tracer.Start(ctx, "ProcessOrder")
defer span.End()
// Add attributes
span.SetAttributes(
attribute.String("order.id", order.ID),
attribute.Int("order.items", len(order.Items)),
attribute.Float64("order.total", order.Total),
)
// Validate order (creates child span)
if err := validateOrder(ctx, order); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "validation failed")
return err
}
// Fulfill order
if err := fulfillOrder(ctx, order); err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, "fulfillment failed")
return err
}
span.SetStatus(codes.Ok, "order processed successfully")
return nil
}
func validateOrder(ctx context.Context, order Order) error {
_, span := otel.Tracer("order-service").Start(ctx, "validateOrder")
defer span.End()
// Validation logic...
return nil
}
HTTP ミドルウェアの計装
import (
"net/http"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)
func main() {
// Wrap handler with automatic tracing
handler := http.HandlerFunc(orderHandler)
wrappedHandler := otelhttp.NewHandler(handler, "order-handler")
http.Handle("/orders", wrappedHandler)
http.ListenAndServe(":8080", nil)
}
// Manual instrumentation for more control
func orderHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
tracer := otel.Tracer("order-service")
ctx, span := tracer.Start(ctx, "orderHandler")
defer span.End()
// Extract order ID from request
orderID := r.URL.Query().Get("id")
span.SetAttributes(attribute.String("order.id", orderID))
// Process order with propagated context
order, err := fetchOrder(ctx, orderID)
if err != nil {
span.RecordError(err)
http.Error(w, "Order not found", http.StatusNotFound)
return
}
// ... handle response
}
Prometheus メトリクス
インストール
go get github.com/prometheus/client_golang/prometheus
go get github.com/prometheus/client_golang/prometheus/promhttp
メトリクスの種類とパターン
package metrics
import (
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promauto"
)
var (
// Counter: Monotonically increasing value
httpRequestsTotal = promauto.NewCounterVec(
prometheus.CounterOpts{
Name: "http_requests_total",
Help: "Total number of HTTP requests",
},
[]string{"method", "path", "status"},
)
// Gauge: Value that can go up or down
activeConnections = promauto.NewGauge(
prometheus.GaugeOpts{
Name: "active_connections",
Help: "Number of active connections",
},
)
// Histogram: Observations bucketed by value
httpRequestDuration = promauto.NewHistogramVec(
prometheus.HistogramOpts{
Name: "http_request_duration_seconds",
Help: "HTTP request duration in seconds",
Buckets: prometheus.DefBuckets, // [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10]
},
[]string{"method", "path"},
)
// Summary: Similar to histogram but calculates quantiles
dbQueryDuration = promauto.NewSummaryVec(
prometheus.SummaryOpts{
Name: "db_query_duration_seconds",
Help: "Database query duration",
Objectives: map[float64]float64{0.5: 0.05, 0.9: 0.01, 0.99: 0.001},
},
[]string{"query_type"},
)
)
メトリクスミドルウェア
import (
"net/http"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
// Metrics middleware that instruments all HTTP handlers
func MetricsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
start := time.Now()
// Track active connections
activeConnections.Inc()
defer activeConnections.Dec()
// Wrap response writer to capture status code
rw := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
// Call next handler
next.ServeHTTP(rw, r)
// Record metrics
duration := time.Since(start).Seconds()
httpRequestDuration.WithLabelValues(r.Method, r.URL.Path).Observe(duration)
httpRequestsTotal.WithLabelValues(r.Method, r.URL.Path, strconv.Itoa(rw.statusCode)).Inc()
})
}
type responseWriter struct {
http.ResponseWriter
statusCode int
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
// Expose metrics endpoint
func main() {
http.Handle("/metrics", promhttp.Handler())
handler := MetricsMiddleware(http.HandlerFunc(orderHandler))
http.Handle("/orders", handler)
http.ListenAndServe(":8080", nil)
}
カスタムメトリクスの例
func ProcessPayment(ctx context.Context, payment Payment) error {
timer := prometheus.NewTimer(dbQueryDuration.WithLabelValues("payment_insert"))
defer timer.ObserveDuration()
// Process payment
if err := db.InsertPayment(payment); err != nil {
httpRequestsTotal.WithLabelValues("POST", "/payments", "500").Inc()
return err
}
httpRequestsTotal.WithLabelValues("POST", "/payments", "200").Inc()
return nil
}
slog による構造化ログ
基本設定(Go 1.21 以降)
package main
import (
"context"
"log/slog"
"os"
)
func initLogger() *slog.Logger {
// JSON logger for production
handler := slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{
Level: slog.LevelInfo,
AddSource: true, // Include file:line information
})
logger := slog.New(handler)
slog.SetDefault(logger) // Set as default logger
return logger
}
func main() {
logger := initLogger()
logger.Info("service starting",
"service", "order-service",
"version", "1.0.0",
"port", 8080,
)
}
コンテキスト対応ロギング
import (
"context"
"log/slog"
"go.opentelemetry.io/otel/trace"
)
// Add trace context to logger
func LoggerWithTrace(ctx context.Context) *slog.Logger {
span := trace.SpanFromContext(ctx)
spanCtx := span.SpanContext()
return slog.With(
"trace_id", spanCtx.TraceID().String(),
"span_id", spanCtx.SpanID().String(),
)
}
func HandleRequest(ctx context.Context, req Request) error {
logger := LoggerWithTrace(ctx)
logger.Info("processing request",
"request_id", req.ID,
"method", req.Method,
"path", req.Path,
)
if err := processRequest(ctx, req); err != nil {
logger.Error("request failed",
"error", err,
"duration_ms", time.Since(req.StartTime).Milliseconds(),
)
return err
}
logger.Info("request completed successfully",
"duration_ms", time.Since(req.StartTime).Milliseconds(),
)
return nil
}
ログレベルと構造化フィールド
func ProcessOrder(ctx context.Context, order Order) error {
logger := LoggerWithTrace(ctx).With(
"order_id", order.ID,
"user_id", order.UserID,
)
logger.Debug("validating order", "items", len(order.Items))
if len(order.Items) == 0 {
logger.Warn("empty order received")
return ErrEmptyOrder
}
logger.Info("order validation passed")
if err := fulfillOrder(ctx, order); err != nil {
logger.Error("fulfillment failed",
"error", err,
slog.Group("order_details",
"total", order.Total,
"items", len(order.Items),
),
)
return err
}
logger.Info("order processed successfully",
"total", order.Total,
)
return nil
}
ヘルスチェックとグレースフルシャットダウン
ヘルスチェックエンドポイント
import (
"context"
"database/sql"
"encoding/json"
"net/http"
"time"
)
type HealthChecker struct {
db *sql.DB
// Add other dependencies
}
type HealthStatus struct {
Status string `json:"status"`
Version string `json:"version"`
Checks map[string]string `json:"checks"`
Timestamp time.Time `json:"timestamp"`
}
// Liveness probe - is the app running?
func (hc *HealthChecker) LivenessHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"status": "alive",
})
}
// Readiness probe - is the app ready to serve traffic?
func (hc *HealthChecker) ReadinessHandler(w http.ResponseWriter, r *http.Request) {
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second)
defer cancel()
status := HealthStatus{
Status: "ready",
Version: "1.0.0",
Checks: make(map[string]string),
Timestamp: time.Now(),
}
// Check database
if err := hc.db.PingContext(ctx); err != nil {
status.Status = "not_ready"
status.Checks["database"] = "unhealthy: " + err.Error()
w.WriteHeader(http.StatusServiceUnavailable)
} else {
status.Checks["database"] = "healthy"
}
// Add more dependency checks (Redis, external APIs, etc.)
w.Header().Set("Content-Type", "application/json")
if status.Status == "ready" {
w.WriteHeader(http.StatusOK)
}
json.NewEncoder(w).Encode(status)
}
グレースフルシャットダウン
import (
"context"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
func main() {
// Initialize tracer
tp, err := initTracer("order-service")
if err != nil {
log.Fatal(err)
}
// Setup HTTP server
server := &http.Server{
Addr: ":8080",
Handler: setupRoutes(),
}
// Channel for shutdown signals
shutdown := make(chan os.Signal, 1)
signal.Notify(shutdown, os.Interrupt, syscall.SIGTERM)
// Start server in goroutine
go func() {
slog.Info("server starting", "port", 8080)
if err := server.ListenAndServe(); err != http.ErrServerClosed {
log.Fatal(err)
}
}()
// Wait for shutdown signal
<-shutdown
slog.Info("shutdown signal received")
// Create shutdown context with timeout
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Shutdown HTTP server
slog.Info("shutting down HTTP server")
if err := server.Shutdown(ctx); err != nil {
slog.Error("HTTP server shutdown error", "error", err)
}
// Shutdown tracer provider (flush spans)
slog.Info("shutting down tracer")
if err := tp.Shutdown(ctx); err != nil {
slog.Error("tracer shutdown error", "error", err)
}
slog.Info("shutdown complete")
}
計装の完全な例
package main
import (
"context"
"database/sql"
"log/slog"
"net/http"
"os"
"time"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
)
type Server struct {
db *sql.DB
logger *slog.Logger
}
func (s *Server) orderHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
// Get tracer and create span
tracer := otel.Tracer("order-service")
ctx, span := tracer.Start(ctx, "orderHandler")
defer span.End()
// Create context-aware logger with trace ID
logger := s.logger.With(
"trace_id", span.SpanContext().TraceID().String(),
"request_id", r.Header.Get("X-Request-ID"),
)
orderID := r.URL.Query().Get("id")
span.SetAttributes(attribute.String("order.id", orderID))
logger.Info("fetching order", "order_id", orderID)
// Fetch order from database
order, err := s.fetchOrder(ctx, orderID)
if err != nil {
span.RecordError(err)
logger.Error("failed to fetch order", "error", err)
http.Error(w, "Order not found", http.StatusNotFound)
return
}
logger.Info("order fetched successfully",
"order_id", orderID,
"items", len(order.Items),
)
// Return order as JSON
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(order)
}
func (s *Server) fetchOrder(ctx context.Context, orderID string) (*Order, error) {
_, span := otel.Tracer("order-service").Start(ctx, "fetchOrder")
defer span.End()
// Time database query
start := time.Now()
var order Order
err := s.db.QueryRowContext(ctx, "SELECT * FROM orders WHERE id = ?", orderID).Scan(&order)
duration := time.Since(start).Seconds()
dbQueryDuration.WithLabelValues("select_order").Observe(duration)
return &order, err
}
func setupRoutes(s *Server, hc *HealthChecker) http.Handler {
mux := http.NewServeMux()
// Health endpoints (no tracing needed)
mux.HandleFunc("/health", hc.LivenessHandler)
mux.HandleFunc("/ready", hc.ReadinessHandler)
mux.Handle("/metrics", promhttp.Handler())
// Business endpoints (with tracing)
orderHandler := http.HandlerFunc(s.orderHandler)
mux.Handle("/orders", otelhttp.NewHandler(orderHandler, "orders"))
// Wrap everything with metrics middleware
return MetricsMiddleware(mux)
}
判断ツリー
OpenTelemetry を使用する場面
OpenTelemetry を使用する場合:
- 複数のサービスで構成される分散システムを構築する
- サービス境界を越えてリクエストをトレースする必要がある
- マイクロサービスのパフォーマンス問題をデバッグする
- ベンダーに依存しないオブザーバビリティが必要(バックエンドを簡単に切り替えられる)
- トレース、メトリクス、ログ間の相関が必要である
OpenTelemetry を使用しない場合:
- シンプルなモノリシックアプリケーションを構築する
- パフォーマンスのオーバーヘッドが重大な問題となる(サンプリングを検討してください)
- チームにオブザーバビリティのインフラストラクチャ(Jaeger、Zipkin)がない
Prometheus を使用する場面
Prometheus を使用する場合:
- 監視とアラートに時系列メトリクスが必要である
- 運用ダッシュボード(Grafana)を構築する
- SLO の達成状況を測定するために SLI を計測する
- ビジネスメトリクス(リクエスト数/秒、コンバージョン率)を追跡する
- Kubernetes/コンテナ化された環境である
Prometheus を使用しない場合:
- カーディナリティの高いメトリクスが必要である(Prometheus には上限があります)
- メトリクスを長期間保存する必要がある(Thanos/Cortex を使用してください)
- プッシュ型メトリクスが必要である(Prometheus はプル型です)
slog を使用する場面
slog を使用する場合:
- Go 1.21 以降のプロジェクトである(標準ライブラリで、依存関係が不要)
- JSON 出力による構造化ログが必要である
- 割り当てを最小限に抑えた高性能ロギングが必要である
- ログ集約システム(Loki、ELK)と統合する
slog を使用しない場合:
- Go < 1.21 である(代わりに zap または zerolog を使用してください)
- 複雑なログルーティングやフィルタリングが必要である(zap を使用してください)
- 非常に特定の機能(監査証跡など)が必要である
サンプリング戦略の決定
常にサンプリングする場合:
- 開発/ステージング環境
- 総トラフィックが 100 リクエスト/秒未満
- 特定の問題をデバッグする場合
確率的サンプリングを行う場合:
- トラフィックが中程度の本番環境(100~10K リクエスト/秒)
- 通常のサンプルレート:1~10%
テールベースサンプリングを行う場合:
- トラフィックの多い本番環境(>10K リクエスト/秒)
- エラーと処理の遅いリクエストのみをサンプリング
- テールサンプリングプロセッサー(OpenTelemetry Collector)が必要
避けるべきアンチパターン
❌ コンテキストを伝播しない
誤り:トレースコンテキストを途切れさせる
func processOrder(order Order) error {
// Creates new context, loses trace!
ctx := context.Background()
return validateOrder(ctx, order)
}
正しい方法:呼び出しチェーン全体でコンテキストを伝播する
func processOrder(ctx context.Context, order Order) error {
// Propagates trace context
return validateOrder(ctx, order)
}
❌ カーディナリティの爆発
誤り:ラベル値に上限がない
// user_id can have millions of values!
httpRequests.WithLabelValues(r.Method, r.URL.Path, userID).Inc()
正しい方法:上限のあるラベルを使用する
// Only method and path (bounded values)
httpRequests.WithLabelValues(r.Method, r.URL.Path).Inc()
// Track user-specific metrics separately if needed
❌ 機密データをログに記録する
誤り:個人識別情報(PII)やシークレットを露出させる
logger.Info("user login",
"email", user.Email, // PII!
"password", user.Password, // CRITICAL!
"token", authToken, // SECRET!
)
正しい方法:機密情報をマスキングする
logger.Info("user login",
"user_id", user.ID, // Safe identifier
"method", "password",
)
❌ スパンを終了しない
誤り:スパンによってメモリリークが発生する
func processOrder(ctx context.Context) error {
ctx, span := tracer.Start(ctx, "processOrder")
// Missing defer span.End()!
if err := validate(); err != nil {
return err // Span never closed!
}
return nil
}
正しい方法:常に span.End() を defer で呼び出す
func processOrder(ctx context.Context) error {
ctx, span := tracer.Start(ctx, "processOrder")
defer span.End() // Always runs
if err := validate(); err != nil {
span.RecordError(err)
return err
}
return nil
}
❌ メトリクスを同期的にエクスポートする
誤り:メトリクスのエクスポートでリクエストをブロックする
// Synchronous export blocks HTTP handler
exporter := jaeger.New(jaeger.WithCollectorEndpoint(...))
tp := sdktrace.NewTracerProvider(
sdktrace.WithSyncer(exporter), // BAD: Synchronous!
)
正しい方法:非同期エクスポートにはバッチ処理を使用する
// Batching exports asynchronously
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter), // GOOD: Async batching
)
❌ グレースフルシャットダウンがない
誤り:シャットダウン時にトレースが失われる
func main() {
tp, _ := initTracer("service")
// Missing shutdown - spans lost!
http.ListenAndServe(":8080", nil)
}
正しい方法:エクスポーターを適切にシャットダウンする
func main() {
tp, _ := initTracer("service")
defer tp.Shutdown(context.Background())
// Handle signals and graceful shutdown
server.ListenAndServe()
}
ベストプラクティス
- コンテキストの伝播:呼び出しチェーン全体で常に
context.Contextを渡します - 上限のあるラベル:メトリクスのラベルのカーディナリティを 1000 通り未満に抑えます
- サンプリング:トラフィックの多い本番環境では確率的サンプリングを使用します
- 相関 ID:ログに trace_id を含めて相関付けに利用します
- ヘルスチェック:
/health(ライブネス)と/ready(レディネス)の両方を実装します - グレースフルシャットダウン:終了前にトレースとメトリクスをフラッシュします
- エラーの記録:自動エラー追跡には
span.RecordError()を使用します - メトリクスの命名:Prometheus の命名規則(
_total、_seconds)に従います - ログレベル:適切なレベル(Debug、Info、Warn、Error)を使用します
- 自動計装:可能な場合は HTTP/gRPC にミドルウェアを使用します
メトリクスの命名規則
Prometheus のベストプラクティスに従います。
カウンターメトリクス(常に増加):
http_requests_total(http_requestsではない)payment_transactions_totalerrors_total
ゲージメトリクス(増減する):
active_connectionsqueue_sizememory_usage_bytes
ヒストグラム/サマリーメトリクス(観測値):
http_request_duration_seconds(_millisecondsではない)db_query_duration_secondsresponse_size_bytes
ラベルの命名:
http_methodではなくmethodを使用しますstatus_codeやhttp_statusではなくstatusを使用します- camelCase ではなく snake_case を使用します
リソース
公式ドキュメント:
- OpenTelemetry Go:https://opentelemetry.io/docs/instrumentation/go/
- Prometheus クライアントライブラリ:https://github.com/prometheus/client_golang
- Go slog パッケージ:https://pkg.go.dev/log/slog
最近のガイド(2025年):
- 「Go のオブザーバビリティ:2025 年に現場のエンジニアが語ること」(Quesma Blog)
- 「OpenTelemetry メトリクスによる Go アプリの監視」(Better Stack、2025年)
- Prometheus のベストプラクティス:https://prometheus.io/docs/practices/naming/
関連スキル:
- golang-web-frameworks:HTTP サーバーのパターンとミドルウェア
- golang-testing-strategies:計装されたコードのテスト
- verification-before-completion:オブザーバビリティの設定を検証する
クイックリファレンス
OpenTelemetry を初期化する
tp, _ := initTracer("service-name")
defer tp.Shutdown(context.Background())
スパンを作成する
ctx, span := otel.Tracer("name").Start(ctx, "operation")
defer span.End()
span.SetAttributes(attribute.String("key", "value"))
メトリクスを定義する
counter := promauto.NewCounterVec(opts, []string{"label"})
histogram := promauto.NewHistogramVec(opts, []string{"label"})
構造化ログ
logger := slog.With("trace_id", traceID)
logger.Info("message", "key", value)
ヘルスチェック
http.HandleFunc("/health", livenessHandler)
http.HandleFunc("/ready", readinessHandler)
トークンの推定数:~5,000 トークン(エントリーポイント+全コンテンツ) バージョン:1.0.0 最終更新日:2025-12-03