Goaサービスで可観測性を設定するには、ClueとOpenTelemetryの設定が必要です。 このガイドでは、基本的なセットアップ手順を説明します。
まず、必要な依存関係をgo.mod
に追加します:
require (
goa.design/clue
go.opentelemetry.io/otel
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc
)
これらのパッケージは以下を提供します:
clue
: Goaの可観測性ツールキットotel
: OpenTelemetryのコア機能otlpmetricgrpc
とotlptracegrpc
: テレメトリーデータを送信するためのOTLPエクスポーターotelhttp
とotelgrpc
: HTTPとgRPCの自動計装ロガーコンテキストは可観測性セットアップの基盤です。設定と相関IDを アプリケーション全体で伝播します:
// 環境に基づいてロガーフォーマットを設定
format := log.FormatJSON
if log.IsTerminal() {
format = log.FormatTerminal // 開発用の人間が読みやすいフォーマット
}
// フォーマットとスパン追跡を含むベースコンテキストを作成
ctx := log.Context(context.Background(),
log.WithFormat(format), // 出力フォーマットを設定
log.WithFunc(log.Span)) // ログにトレース/スパンIDを含める
// 必要に応じてデバッグロギングを有効化
if *debugf {
ctx = log.Context(ctx, log.WithDebug())
log.Debugf(ctx, "デバッグログが有効化されました")
}
// サービス情報を追加
ctx = log.With(ctx,
log.KV{"service", serviceName},
log.KV{"version", version},
log.KV{"env", environment})
ロガーコンテキストは以下を提供します:
OpenTelemetryのセットアップには、エクスポーターの作成とグローバルプロバイダーの設定が含まれます:
// テレメトリーをコレクターに送信するためのOTLPエクスポーターを作成
spanExporter, err := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint(*coladdr),
otlptracegrpc.WithTLSCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf(ctx, err, "トレーシングの初期化に失敗しました")
}
defer func() {
ctx := log.Context(context.Background())
if err := spanExporter.Shutdown(ctx); err != nil {
log.Errorf(ctx, err, "トレーシングのシャットダウンに失敗しました")
}
}()
metricExporter, err := otlpmetricgrpc.New(ctx,
otlpmetricgrpc.WithEndpoint(*coladdr),
otlpmetricgrpc.WithTLSCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf(ctx, err, "メトリクスの初期化に失敗しました")
}
defer func() {
ctx := log.Context(context.Background())
if err := metricExporter.Shutdown(ctx); err != nil {
log.Errorf(ctx, err, "メトリクスのシャットダウンに失敗しました")
}
}()
// エクスポーターでClueを初期化
cfg, err := clue.NewConfig(ctx,
serviceName,
version,
metricExporter,
spanExporter,
clue.WithResourceAttributes(map[string]string{
"environment": environment,
"region": region,
}))
if err != nil {
log.Fatalf(ctx, err, "可観測性の初期化に失敗しました")
}
clue.ConfigureOpenTelemetry(ctx, cfg)
この設定により、サービスのコアとなるOpenTelemetryインフラストラクチャがセットアップされます。 テレメトリーデータを処理と保存のためにコレクターに送信するエクスポーターを作成します。 また、サービス終了時のデータ損失を防ぐための適切なシャットダウン処理も確保します。 環境やリージョンなどのリソース属性を追加して、テレメトリーデータの効果的な整理とフィルタリングを 支援します。最後に、アプリケーション全体でトレーシングとメトリクス収集を可能にする グローバルOpenTelemetryプロバイダーを初期化します。
HTTPサービスの場合、ハンドラーを可観測性ミドルウェアでラップします:
// Goa HTTPマルチプレクサを作成
mux := goahttp.NewMuxer()
// デバッグエンドポイントをマウント
debug.MountDebugLogEnabler(debug.Adapt(mux)) // 動的ログレベル制御
debug.MountPprofHandlers(debug.Adapt(mux)) // Goプロファイリングエンドポイント
// 正しい順序でミドルウェアを追加(内側から外側):
handler := otelhttp.NewHandler(mux, serviceName) // 3. OpenTelemetry
handler = debug.HTTP()(handler) // 2. デバッグエンドポイント
handler = log.HTTP(ctx)(handler) // 1. リクエストロギング
// 計装されたハンドラーでサーバーを作成
server := &http.Server{
Addr: *httpAddr,
Handler: handler,
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
}
gRPCサービスの場合、インターセプターを使用します:
// 可観測性を備えたgRPCクライアント接続を作成
conn, err := grpc.DialContext(ctx, *serverAddr,
grpc.WithTransportCredentials(insecure.NewCredentials()),
grpc.WithUnaryInterceptor(log.UnaryClientInterceptor()),
grpc.WithStatsHandler(otelgrpc.NewClientHandler()))
// 可観測性を備えたgRPCサーバーを作成
srv := grpc.NewServer(
grpc.UnaryInterceptor(log.UnaryServerInterceptor()),
grpc.StatsHandler(otelgrpc.NewServerHandler()))
ミドルウェア/インターセプターは以下を提供します:
ヘルスチェックは、サービスとその依存関係を監視するのに役立ちます。Clueは ヘルスチェックを実装するための2つの主要なインターフェースを提供します:
Pinger
インターフェースは、単一の依存関係の健全性をチェックする方法を定義します:
type Pinger interface {
// Nameはリモートサービスの名前を返します
Name() string
// Pingはサービスが正常かどうかをチェックします
Ping(context.Context) error
}
Clueは、ヘルスチェックエンドポイントをpingするデフォルトのHTTPベースの実装を提供します:
// データベースサービス用のpingerを作成
dbPinger := health.NewPinger("database", "db:8080",
health.WithScheme("https"), // HTTPSを使用(デフォルト: http)
health.WithPath("/health")) // カスタムパス(デフォルト: /livez)
// Redis用のpingerを作成
redisPinger := health.NewPinger("redis", "redis:6379",
health.WithPath("/ping")) // Redisのヘルスエンドポイント
特殊なケース用にカスタムpingerを実装することもできます:
type CustomPinger struct {
name string
db *sql.DB
}
func (p *CustomPinger) Name() string { return p.name }
func (p *CustomPinger) Ping(ctx context.Context) error {
return p.db.PingContext(ctx)
}
Checker
インターフェースは、複数のpingerを集約し、全体的な健全性ステータスを提供します:
type Checker interface {
// Checkはすべての依存関係の健全性ステータスを返します
Check(context.Context) (*Health, bool)
}
// Healthには詳細なステータス情報が含まれます
type Health struct {
Uptime int64 // サービスの稼働時間(秒)
Version string // サービスのバージョン
Status map[string]string // 各依存関係のステータス
}
複数の依存関係を持つチェッカーを作成:
// 複数のpingerを持つヘルスチェッカーを作成
checker := health.NewChecker(
health.NewPinger("database", *dbAddr),
health.NewPinger("cache", *cacheAddr),
health.NewPinger("search", *searchAddr),
&CustomPinger{name: "custom", db: db},
)
// チェッカーからHTTPハンドラーを作成
check := health.Handler(checker)