😺

Go で PostgreSQL に pgx と OpenTelemetry を使ってクエリをトレースする

2024/10/06に公開

はじめに

Go で PostgreSQL に pgx と OpenTelemetry を使ってクエリをトレースする方法を紹介します。

目標

最終的にこのようなトレースを取得できることを目標とします。

成果物

以下のリポジトリにサンプルコードを置いています。

https://github.com/tetsuya28/samples/tree/main/go/opentelemetry/pgx-otel

利用ライブラリ

主に利用しているライブラリは以下の通りです。

https://github.com/jackc/pgx

https://github.com/exaring/otelpgx

https://github.com/open-telemetry/opentelemetry-go

コード

Tracer provider に渡すオプションの準備

まずは以下のように tracer provider に渡すオプションを準備します。

検証用なので sdktrace.AlwaysSample() を利用して常時サンプリングするようにしています。

sdktrace.WithResource() でリソースを指定しています。

import (
	"go.opentelemetry.io/otel/sdk/resource"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
	semconv "go.opentelemetry.io/otel/semconv/v1.26.0"
)

opts := []sdktrace.TracerProviderOption{
	sdktrace.WithSampler(sdktrace.AlwaysSample()),
	sdktrace.WithResource(
		resource.NewWithAttributes(
			semconv.SchemaURL,
			semconv.ServiceNameKey.String("pgx-otel"),
			semconv.ServiceVersion("v0.0.0"),
		),
	),
}

Exporter に関してはローカルでの検証用と Grafana Cloud を利用する場合の 2 つを用意しています。

ローカルでの検証用の場合は stdouttrace を利用して標準出力に出力します。

import (
	"go.opentelemetry.io/otel/exporters/stdout/stdouttrace"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

exporter, err := stdouttrace.New(stdouttrace.WithPrettyPrint())
if err != nil {
	panic(err)
}
opts = append(opts, sdktrace.WithBatcher(exporter))

Grafana Cloud を利用する場合は以下のように設定します。

Grafana Cloud へのトレース情報の送信は現時点では HTTP のみ対応しているため otlptracehttp を利用します。

また、次の環境変数を Grafana Cloud の設定画面から取得して設定します。

  • OTEL_EXPORTER_OTLP_ENDPOINT
  • OTEL_EXPORTER_OTLP_PROTOCOL
  • OTEL_EXPORTER_OTLP_HEADERS
import (
	"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

otelClient := otlptracehttp.NewClient()
exporter, err := otlptrace.New(ctx, otelClient)
if err != nil {
	panic(err)
}
opts = append(opts, sdktrace.WithBatcher(exporter))

Tracer provider の生成

準備したオプションを利用して Tracer provider を生成します。

ここでは batcher を利用しているため、最後に ForceFlush()Shutdown() を呼び出して終了時にリソースを解放します。

import (
	"go.opentelemetry.io/otel"
	sdktrace "go.opentelemetry.io/otel/sdk/trace"
)

tp := sdktrace.NewTracerProvider(opts...)
otel.SetTracerProvider(tp)

defer func() {
	if err := tp.ForceFlush(ctx); err != nil {
		panic(err)
	}

	if err := tp.Shutdown(ctx); err != nil {
		panic(err)
	}
}()

pgx の設定

ここではローカルの PostgreSQL に接続する場合の設定例を示します。
pgxpool でプールの設定を初期化した後、 otelpgx.NewTracer() を利用して Tracer を設定します。

このコンフィグを用いて、 pgxpool を生成します。

import (
	"github.com/exaring/otelpgx"
	"github.com/jackc/pgx/v5/pgxpool"
)

url := "postgres://usr:pw@localhost:5432/db?sslmode=disable"
cfg, err := pgxpool.ParseConfig(url)
if err != nil {
	return err
}

cfg.ConnConfig.Tracer = otelpgx.NewTracer(otelpgx.WithTracerProvider(tp))

conn, err := pgxpool.NewWithConfig(ctx, cfg)
if err != nil {
	return err
}
defer conn.Close()

クエリの実行

あとは通常通りクエリを実行します。

row, err := conn.Query(ctx, "SELECT 1")
if err != nil {
	return err
}
defer row.Close()

これでクエリをトレースできます。

設定したトレースバックエンドでトレース情報を確認してみてください。

Discussion