コンテンツにスキップ

分散トレース@クライアントパッケージ

はじめに

本サイトにつきまして、以下をご認識のほど宜しくお願いいたします。


01. TracerProvider

TracerProviderとは

OpenTelemetryをセットアップし、スパンを作成する機能を提供する。

Goなら、go.opentelemetry.io/otel/sdkパッケージからコールできる。

NewTracerProvider関数に分散トレースのオプションを渡す。

func InitTracerProvider(serviceName string) (*sdktrace.TracerProvider, func(), error) {

    ...

    tracerProvider := sdktrace.New(
        // Exporterを設定する
        sdktrace.WithBatcher(exporter),
        // Resourceを設定する
        sdktrace.WithResource(resourceWithAttributes),
        // Samplerを設定する
        sdktrace.WithSampler(sdktrace.AlwaysSample()),
        // Span Processorを設定する
        sdktrace.WithSpanProcessor(batchSpanProcessor),
    )

    tracerProvider := sdktrace.New(options...)

    ...

    // TraceProviderインターフェースを実装する構造体を作成する
    otel.SetTracerProvider(tracerProvider)

    propagator := autoprop.NewTextMapPropagator()

    // 受信したリクエストのCarrierからトレースコンテキストを抽出し、送信するリクエストのCarrierにトレースコンテキストを注入できるようにする。
    otel.SetTextMapPropagator(
        // Composit Propagatorを設定する
        propagator
    )

    propagatorList := propagator.Fields()

    sort.Strings(propagatorList)

    // ログにpropagator名を出力しておく
    log.Printf("Info: Propagator %v initialize successfully", propagatorList)

    cleanUp := func() {

        ctx, cancel := context.WithTimeout(
            context.Background(),
            5 * time.Second,
        )

        // タイムアウトの場合に処理を中断する
        defer cancel()

        // Span Processor内の処理中スパンをExporterに送信する
        if err := tracerProvider.ForceFlush(ctx); err != nil {
            log.Printf("Failed to force flush trace provider %v", err)
            return
        }

        log.Print("Info: Trace provider shutdown successfully")
    }

    log.Print("Info: Tracer provider initialize successfully")

    return cleanUp, nil
}
package main

func main()  {

    cleanUp, nil := InitTracerProvider()
    defer cleanUp()

    ...
}


TracerProviderOption

分散トレースのオプションを持つTracerProviderOption構造体を別に作成し、TracerProviderに渡してもよい。

func InitTracerProvider(serviceName string) (*sdktrace.TracerProvider, func(), error) {

    ...

    options := []trace.TracerProviderOption{
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(resourceWithAttributes),
        sdktrace.WithSampler(sdktrace.AlwaysSample()),
        sdktrace.WithSpanProcessor(batchSpanProcessor),
    }

    tracerProvider := sdktrace.New(options...)

    ...

    // TraceProviderインターフェースを実装する構造体を作成する
    otel.SetTracerProvider(tracerProvider)

    propagator := autoprop.NewTextMapPropagator()

    // 受信したリクエストのCarrierからトレースコンテキストを抽出し、送信するリクエストのCarrierにトレースコンテキストを注入できるようにする。
    otel.SetTextMapPropagator(
        // Composit Propagatorを設定する
        propagator
    )

    propagatorList := propagator.Fields()

    sort.Strings(propagatorList)

    // ログにpropagator名を出力しておく
    log.Printf("Info: Propagator %v initialize successfully", propagatorList)

    cleanUp := func() {

        ctx, cancel := context.WithTimeout(
            context.Background(),
            5 * time.Second
        )

        // タイムアウトの場合に処理を中断する
        defer cancel()

        // Span Processor内の処理中スパンをExporterに送信する
        if err := tracerProvider.ForceFlush(ctx); err != nil {
            log.Printf("Failed to force flush trace provider %v", err)
            return
        }

        log.Print("Info: Trace provider shutdown successfully")
    }

    log.Print("Info: Tracer provider initialize successfully")

    return cleanUp, nil
}
package main

func main()  {

    cleanUp, nil := InitTracerProvider()
    defer cleanUp()

    ...
}


分散トレースの無効化

▼ TracerProviderを実行しない

一番単純な方法として、スパンを作成しない場合は、TracerProviderのセットアップをコールしないことである。

以下のように条件分岐を実装する。

package main

import (
    "os"
)

func main() {

    ...

    traceEnabled := os.Getenv("TRACE_ENABLED")

    // TRACE_ENABLEDが有効になっている場合に、分散トレースのスパンを作成する
    if traceEnabled {
        // TracerProviderに関する一連の処理
    }

    ...

}

マイクロサービスで使用するConfigMapにて、分散トレースの有効化を実行環境別に切り替えられるようにするとよい。

kind: ConfigMap
apiVersion: v1
metadata:
  name: foo-app
  env: tes
data:
  # テスト環境では分散トレースを無効化する
  TRACE_ENABLED: "false"
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: foo-app
  env: stg
data:
  # ステージング環境では分散トレースを無効化する
  TRACE_ENABLED: "true"
---
kind: ConfigMap
apiVersion: v1
metadata:
  name: foo-app
  env: prd
data:
  # 本番環境では分散トレースを無効化する
  TRACE_ENABLED: "true"

▼ NoopTracerProviderを使用する

TracerProviderのデフォルト値である。

多くの言語で、TracerProviderのインターフェースの実装である。

また、TracerProviderを意図的に無効化したい場合 (分散トレースが不要な開発環境) にも役立つが、SDK固有の一部のメソッド (ForceFlush関数) がある場合は使用できない。

Go (v1.20) からgo.opentelemetry.io/otel/trace/noopに移動したため、アップグレード時はパッケージを変更する必要がある。

type TracerProvider interface {
    ...
}

type noopTracerProvider struct{
    embedded.TracerProvider
}

▼ Samplerを無効化する

Samplerを無効化すると、スパンの作成を無効化できる。

package trace

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

// newTracerProvider TracerProviderを作成する
func newTracerProvider(exporter sdktrace.SpanExporter) *sdktrace.TracerProvider {

    resourceWithAttributes := resource.NewWithAttributes(
        semconv.SchemaURL,
        semconv.ServiceName("<マイクロサービス名>"),
        semconv.String("system.name", "<システム名>"),
        semconv.String("environment", "<実行環境名>"),
    )

    sampler := newSampler()

    // BatchSpanProcessorで複数のスパンを圧縮し、送信サイズを小さくする
    batchSpanProcessor := sdktrace.NewBatchSpanProcessor(exporter)

    // OpenTelemetry CollectorでW3C形式からX-Ray形式にIDを変換できるため、ここではW3C形式でIDを作成する
    tracerProvider := sdktrace.NewTracerProvider(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(resourceWithAttributes),
        sdktrace.WithSampler(sampler),
        sdktrace.WithSpanProcessor(batchSpanProcessor),
    )

    return tracerProvider
}

// newSampler Samplerを作成する
func newSampler() sdktrace.Sampler {

    traceEnabled := os.Getenv("TRACE_ENABLED")

    // TRACE_ENABLEDを無効化している場合
    if !traceEnabled {
        return sdktrace.NeverSample()
    }

    // Tail-based方式のサンプリングを採用し、クライアント側のサンプリング率は推奨値の100%とする
    return sdktrace.ParentBased(sdktrace.TraceIDRatioBased(1.0))
}

OTEL_SDK_DISABLEDを有効化する

OTEL_SDK_DISABLEDを有効化すると、実装をそのままで分散トレースを無効化できる。

ただし、言語 (例:Go) によってはサポートしていない場合がある。


01-02. スパンの作成

スパンの構造

▼ ルートスパン

ルートスパンの構造である。

{
  "name": "hello",
  "context": {
      # ルートスパンのトレースID
      "trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2",
      # ルートスパンのスパンID
      "span_id": "0x051581bf3cb55c13",
    },
  # ルートスパンのため、親スパンのスパンIDがない
  "parent_id": null,
  "start_time": "2022-04-29T18:52:58.114201Z",
  "end_time": "2022-04-29T18:52:58.114687Z",
  "attributes": {"http.route": "some_route1"},
  "events":
    [
      {
        "name": "Guten Tag!",
        "timestamp": "2022-04-29T18:52:58.114561Z",
        "attributes": {"event_attributes": 1},
      },
    ],
}

▼ 親スパンX

1つ目の親スパンの構造である。

{
  "name": "hello-greetings",
  "context": {
      # ルートスパンのトレースID
      "trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2",
      # 親スパンXのスパンID
      "span_id": "0x5fb397be34d26b51",
    },
  # 親スパンXのスパンID (ルートスパンのスパンIDと一致する)
  "parent_id": "0x051581bf3cb55c13",
  "start_time": "2022-04-29T18:52:58.114304Z",
  "end_time": "2022-04-29T22:52:58.114561Z",
  "attributes": {"http.route": "some_route2"},
  "events":
    [
      {
        "name": "hey there!",
        "timestamp": "2022-04-29T18:52:58.114561Z",
        "attributes": {"event_attributes": 1},
      },
      {
        "name": "bye now!",
        "timestamp": "2022-04-29T18:52:58.114585Z",
        "attributes": {"event_attributes": 1},
      },
    ],
}

▼ 親スパンY

2つ目の親スパンの構造である。

{
  "name": "hello-salutations",
  "context": {
      # ルートスパンのトレースID
      "trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2",
      # 親スパンYのスパンID
      "span_id": "0x93564f51e1abe1c2",
    },
  # 親スパンYのスパンID (ルートスパンのスパンIDと一致する)
  "parent_id": "0x051581bf3cb55c13",
  "start_time": "2022-04-29T18:52:58.114492Z",
  "end_time": "2022-04-29T18:52:58.114631Z",
  "attributes": {"http.route": "some_route3"},
  "events":
    [
      {
        "name": "hey there!",
        "timestamp": "2022-04-29T18:52:58.114561Z",
        "attributes": {"event_attributes": 1},
      },
    ],
}


Spanステータス

▼ Spanステータスとは

Spanに紐づく処理の成否を表す。

▼ Unset

スパンに対応する処理が成功したことを表す。

▼ Error

スパンに対応する処理が失敗したことを表す。

▼ Ok

スパンに対応する処理を成功したと強制的にみなすことを表す。

成功以外にしたくない場合に使用する。


Span種別

▼ Span種別とは

スパンの作成場所の種類を表す。

▼ Unspecified

種類がないことを表す。

package main

import (
    "go.opentelemetry.io/otel"
)

func main()  {

    ...

    foo()

    ...
}

func foo()  {

    // Tracerを作成する
    var tracer = otel.Tracer("計装パッケージ名")

    ctx, span := tracer.Start(
        ctx,
        "foo",
        trace.WithSpanKind(0),
    )

    defer span.End()
}

▼ Internal

アプリケーションの内部処理に関する情報を持つ。

package main

import (
    "go.opentelemetry.io/otel"
)

func main()  {

    ...

    foo()

    ...
}

func foo()  {

    // Tracerを作成する
    var tracer = otel.Tracer("計装パッケージ名")

    ctx, span := tracer.Start(
        ctx,
        "foo",
        trace.WithSpanKind(1),
    )

    defer span.End()
}

▼ Server

クライアントからのリクエストの受信処理に関する情報を持つ。

package main

import (
    "go.opentelemetry.io/otel"
)

func main()  {

    ...

    foo()

    ...
}

func foo()  {

    // Tracerを作成する
    var tracer = otel.Tracer("計装パッケージ名")

    ctx, span := tracer.Start(
        ctx,
        "foo",
        trace.WithSpanKind(2),
    )

    defer span.End()
}

▼ Client

サーバーへのリクエストの送信処理に関する情報を持つ。

package main

import (
    "go.opentelemetry.io/otel"
)

func main()  {

    ...

    foo()

    ...
}

func foo()  {

    // Tracerを作成する
    var tracer = otel.Tracer("計装パッケージ名")

    ctx, span := tracer.Start(
        ctx,
        "foo",
        trace.WithSpanKind(3),
    )

    defer span.End()
}

▼ Producer

パブリッシャー (プロデューサー) からのメッセージの受信処理に関する情報を持つ。

package main

import (
    "go.opentelemetry.io/otel"
)

func main()  {

    ...

    foo()

    ...
}

func foo()  {

    // Tracerを作成する
    var tracer = otel.Tracer("計装パッケージ名")

    ctx, span := tracer.Start(
        ctx,
        "foo",
        trace.WithSpanKind(4),
    )

    defer span.End()
}

▼ Consumer

コンシューマー (サブスクライバー) からのメッセージの送信処理に関する情報を持つ。

package main

import (
    "go.opentelemetry.io/otel"
)

func main()  {

    ...

    foo()

    ...
}

func foo()  {

    // Tracerを作成する
    var tracer = otel.Tracer("計装パッケージ名")

    ctx, span := tracer.Start(
        ctx,
        "foo",
        trace.WithSpanKind(5),
    )

    defer span.End()
}


Spanイベント

▼ Spanイベントとは

スパンの処理時間中の特定時点のイベントを表す。


01-03. エラー時の事後処理

自前のエラーの使用

TracerProviderがスローするエラーを自前のエラーに変更する。

package main

import (
    "error"
    "fmt"

    "go.opentelemetry.io/otel"
)

type ErrorHandler interface {
  Handle(err error)
}

type IgnoreExporterErrorsHandler struct{}

func Handler() ErrorHandler

func (IgnoreExporterErrorsHandler) Handle(err error) {

    switch err.(type) {
    // SpanExporterのエラーの場合は何もしない
    case *SpanExporterError:

    default:
        fmt.Println(err)
    }
}

func main() {

    ...

    otel.SetErrorHandler(IgnoreExporterErrorsHandler{})

    ...
}


未送信スパンの送信

処理の失敗時にSpan Processor内に未送信なスパンがある場合、これを送信し切ってしまう方が良い。

func InitTracerProvider(serviceName string) (*sdktrace.TracerProvider, func(), error) {

    ...

    tracerProvider := sdktrace.New(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(resourceWithAttributes),
        sdktrace.WithSampler(sdktrace.AlwaysSample()),
        sdktrace.WithSpanProcessor(batchSpanProcessor),
    )

    ...

    // TraceProviderインターフェースを実装する構造体を作成する
    otel.SetTracerProvider(tracerProvider)

    propagator := autoprop.NewTextMapPropagator()

    // 受信したリクエストのCarrierからトレースコンテキストを抽出し、送信するリクエストのCarrierにトレースコンテキストを注入できるようにする。
    otel.SetTextMapPropagator(
        // Composit Propagatorを設定する
        propagator
    )

    propagatorList := propagator.Fields()

    sort.Strings(propagatorList)

    // ログにpropagator名を出力しておく
    log.Printf("Info: Propagator %v initialize successfully", propagatorList)

    cleanUp := func() {

        ctx, cancel := context.WithTimeout(
            context.Background(),
            5 * time.Second
        )

        // タイムアウトの場合に処理を中断する
        defer cancel()

        // Span Processor内の処理中スパンをExporterに送信する
        if err := tracerProvider.ForceFlush(ctx); err != nil {
            log.Printf("Failed to force flush trace provider %v", err)
            return
        }

        log.Print("Info: Trace provider shutdown successfully")
    }

    log.Print("Info: Tracer provider initialize successfully")

    return cleanUp, nil
}
package main

func main()  {

    cleanUp, nil := InitTracerProvider()
    defer cleanUp()

    ...
}


Graceful Shutdown処理

TracerProviderは、Graceful Shutdown処理を実行するための関数を持っている。

処理の失敗時にGraceful Shutdown処理を実行する。

これにより、例えば確保しているハードウェアリソースを解放できる。

なお、TracerProviderでGraceful Shutdown処理を実行すれば、ExporterやSpan Processorも連鎖的にGraceful Shutdownできる。

func InitTracerProvider(serviceName string) (*sdktrace.TracerProvider, func(), error) {

    ...

    tracerProvider := sdktrace.New(
        sdktrace.WithBatcher(exporter),
        sdktrace.WithResource(resourceWithAttributes),
        sdktrace.WithSampler(sdktrace.AlwaysSample()),
        sdktrace.WithSpanProcessor(batchSpanProcessor),
    )

    ...

    // TraceProviderインターフェースを実装する構造体を作成する
    otel.SetTracerProvider(tracerProvider)

    propagator := autoprop.NewTextMapPropagator()

    // 受信したリクエストのCarrierからトレースコンテキストを抽出し、送信するリクエストのCarrierにトレースコンテキストを注入できるようにする。
    otel.SetTextMapPropagator(
        // Composit Propagatorを設定する
        propagator
    )

    propagatorList := propagator.Fields()

    sort.Strings(propagatorList)

    // ログにpropagator名を出力しておく
    log.Printf("Info: Propagator %v initialize successfully", propagatorList)

    cleanUp := func() {

        ctx, cancel := context.WithTimeout(
            context.Background(),
            5 * time.Second
        )

        // タイムアウトの場合に処理を中断する
        defer cancel()

        // Graceful Shutdown処理を実行する
        if err := tracerProvider.Shutdown(ctx); err != nil {
            log.Printf("Failed to shutdown tracer provider %v", err)
            return
        }

        log.Print("Info: Trace provider shutdown successfully")
    }

    log.Print("Info: Tracer provider initialize successfully")

    return cleanUp, nil
}
package main

func main()  {

    cleanUp, nil := InitTracerProvider()
    defer cleanUp()

    ...
}


02. Exporter

Exporterとは

スパンの宛先とするスパン収集ツール (例:AWS Distro for OpenTelemetry Collector、Google CloudTrace、OpenTelemetry Collector、など) を決める処理を持つ。


Exporterの種類

▼ Stdout Exporter

標準出力をスパンの宛先とする。

例えばGoの場合、go.opentelemetry.io/otel/exporters/stdout/stdouttraceパッケージからコールできる。

go.opentelemetry.io/otel/sdk/export/パッケージは執筆時点 (2023/09/18時点) で非推奨である。

▼ OTLP HTTP Exporter

OpenTelemetry Collectorをスパンの宛先とし、HTTPでOpenTelemetry Collector接続する。

例えばGoの場合、go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttpパッケージからコールできる。

▼ OTLP gRPC Exporter

OpenTelemetry Collectorをスパンの宛先とし、gRPCでOpenTelemetry Collector接続する。

gRPCクライアントとして、gRPCサーバーに接続可能なアプリケーションで使用できる。

例えばGoの場合、go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpcパッケージからコールできる。

▼ Jaeger Exporter

Jaegerをスパンの宛先とする。

例えばGoの場合、go.opentelemetry.io/otel/exporters/trace/jaegerパッケージからコールできる。

▼ AWS X-Ray Exporter

AWS X-Rayをスパンの宛先とする。

▼ Google CloudTrace Exporter

Google CloudTraceをスパンの宛先とする。

例えばGoの場合、github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/traceパッケージからコールできる。


スパン宛先の設定

Goの場合、WithEndpoint関数を使用して、スパンの宛先 (例:127.0.0.1:4317opentelemetry-collector.foo-namespace.svc.cluster.local:4317、など) を設定する。


エラー時の事後処理

▼ 未送信スパンの送信

▼ Graceful Shutdown処理

Exporterは、Graceful Shutdown処理を実行するための関数を持っている。

なお、TracerProviderでGraceful Shutdown処理を実行すれば、Exporterも連鎖的にGraceful Shutdownできる。

func InitTracerProvider(serviceName string) (*sdktrace.TracerProvider, func(), error) {

    ...

    exporter, err := NewGrpcExporter(ctx)

    // TraceProviderインターフェースを実装する構造体を作成する
    otel.SetTracerProvider(tracerProvider)

    propagator := autoprop.NewTextMapPropagator()

    // 受信したリクエストのCarrierからトレースコンテキストを抽出し、送信するリクエストのCarrierにトレースコンテキストを注入できるようにする。
    otel.SetTextMapPropagator(
        // Composit Propagatorを設定する
        propagator
    )

    propagatorList := propagator.Fields()

    sort.Strings(propagatorList)

    // ログにpropagator名を出力しておく
    log.Printf("Info: Propagator %v initialize successfully", propagatorList)

    cleanUp := func() {

        ctx, cancel := context.WithTimeout(
            context.Background(),
            5 * time.Second
        )

        // タイムアウトの場合に処理を中断する
        defer cancel()

        // Graceful Shutdown処理を実行する
        if err := exporter.Shutdown(ctx); err != nil {
            log.Printf("Failed to shutdown exporter: %v", err)
            return
        }

        log.Print("Info: Trace provider shutdown successfully")
    }

    log.Print("Info: Tracer provider initialize successfully")

    return cleanUp, nil
}

func NewGrpcExporter(ctx context.Context) (*otlptrace.Exporter, error) {

    conn, err := grpc.DialContext(
        ctx,
        // gRPCでOpenTelemetry Collectorに接続する
        "foo-opentelemetry-collector.foo-namespace.svc.cluster.local:4317",
        // 通信は非TLSとする
        grpc.WithTransportCredentials(insecure.NewCredentials()),
        // コネクションを確立できるまで待機する
        grpc.WithBlock(),
    )

    if err != nil {
        log.Printf("Failed to create gRPC connection: %v", err)
        return nil, err
    }

    return otlptracegrpc.New(
        ctx,
        otlptracegrpc.WithGRPCConn(conn),
    )
}
package main

func main()  {

    cleanUp, nil := InitTracerProvider()
    defer cleanUp()

    ...
}


03. ID Generator

ID Generatorとは

特定の監視バックエンドの形式で、トレースIDやスパンIDを作成する。

ID Generatorを使用しない場合、ID GeneratorはW3C Trace Context仕様に沿ったランダムなIDを作成する。

もしW3C Trace Context仕様以外のランダムなIDがよければ、専用のID Generatorを使用する必要がある。


X-Ray仕様

X-Ray仕様のトレースIDやスパンIDを作成する。

package main

func main()  {

    ...

    tp := sdkTrace.NewTracerProvider(
        sdktrace.WithIDGenerator(xray.NewIDGenerator()),
    )

    ...

}


04. Span Processor

Span Processorとは

他の処理コンポーネントを操作する処理を持つ。


Span Processorの種類

▼ Batch Span Processor

テレメトリーファイルを圧縮するバッチ処理を実行し、送信サイズを小さくした上でExporterに渡す。

Exporterがまとめてスパンを送信できるようになるため、スパンの送信でスループットを高められる。

Goの場合、BatchSpanProcessor関数を使用する。

▼ Simple Span Processor

テレメトリーファイルをそのままExporterに渡す。


エラー時の事後処理

▼ 未送信スパンの送信

▼ Graceful Shutdown処理

Span Processorは、Graceful Shutdown処理を実行するための関数を持っている。

なお、TracerProviderでGraceful Shutdown処理を実行すれば、Span Processorも連鎖的にGraceful Shutdownできる。


05. Propagator

Propagatorとは

トレースコンテキストをアップストリーム側マイクロサービスに伝播させる処理を持つ。

Carrierからトレースコンテキストを注入する操作を『注入 (Inject)』、反対に取り出す操作を『抽出 (Extract) 』という。

distributed-trace_propagated


Composite Propagator

複数のPropagatorを持ち、マイクロサービス上での要求に応じて、Propagatorを動的に切り替えられる。


注入/抽出

▼ W3 Trace Contextの場合

func (tc TraceContext) Inject(ctx context.Context, carrier TextMapCarrier) {

    sc := trace.SpanContextFromContext(ctx)
    if !sc.IsValid() {
        return
    }

    if ts := sc.TraceState().String(); ts != "" {
        carrier.Set(tracestateHeader, ts)
    }

    flags := sc.TraceFlags() & trace.FlagsSampled

    // 仕様に沿ったトレースコンテキストを作成する
    var sb strings.Builder
    sb.Grow(2 + 32 + 16 + 2 + 3)
    _, _ = sb.WriteString(versionPart)
    traceID := sc.TraceID()
    spanID := sc.SpanID()
    flagByte := [1]byte{byte(flags)}
    var buf [32]byte
    for _, src := range [][]byte{traceID[:], spanID[:], flagByte[:]} {
        _ = sb.WriteByte(delimiter[0])
        n := hex.Encode(buf[:], src)
        _, _ = sb.Write(buf[:n])
    }

    // Carrierにトレースコンテキストを設定する
    carrier.Set(traceparentHeader, sb.String())
}
func (tc TraceContext) Extract(ctx context.Context, carrier TextMapCarrier) context.Context {

    // Carrierからトレースコンテキストを取得する
    sc := tc.extract(carrier)
    if !sc.IsValid() {
        return ctx
    }
    return trace.ContextWithRemoteSpanContext(ctx, sc)
}

func (tc TraceContext) extract(carrier TextMapCarrier) trace.SpanContext {
    h := carrier.Get(traceparentHeader)
    if h == "" {
        return trace.SpanContext{}
    }

    var ver [1]byte
    if !extractPart(ver[:], &h, 2) {
        return trace.SpanContext{}
    }
    version := int(ver[0])
    if version > maxVersion {
        return trace.SpanContext{}
    }

    var scc trace.SpanContextConfig
    if !extractPart(scc.TraceID[:], &h, 32) {
        return trace.SpanContext{}
    }
    if !extractPart(scc.SpanID[:], &h, 16) {
        return trace.SpanContext{}
    }

    var opts [1]byte
    if !extractPart(opts[:], &h, 2) {
        return trace.SpanContext{}
    }
    if version == 0 && (h != "" || opts[0] > 2) {
        return trace.SpanContext{}
    }

    scc.TraceFlags = trace.TraceFlags(opts[0]) & trace.FlagsSampled
    scc.TraceState, _ = trace.ParseTraceState(carrier.Get(tracestateHeader))
    scc.Remote = true

    sc := trace.NewSpanContext(scc)
    if !sc.IsValid() {
        return trace.SpanContext{}
    }

    return sc
}


05-02. トレースコンテキスト仕様

トレースコンテキスト仕様の種類

▼ OpenTelemetry

OpenTelemetryが定めるトレースコンテキスト仕様である。

例えばGoの場合、go.opentelemetry.io/otel/propagationパッケージからコールできる。

▼ X-Ray

X-Rayが定めるトレースコンテキスト仕様である。


トレースコンテキスト仕様の設定

いずれのトレースコンテキスト仕様を使用するかをクライアント側とサーバー側の両方で設定する必要がある。

例えばGoの場合、SetTextMapPropagator関数を使用してトレースコンテキスト仕様を設定する。

// クライアント側マイクロサービス
// 前のマイクロサービスにとってはサーバー側にもなる
func NewTracerProvider() {

    // 監視バックエンドが対応するトレースコンテキスト仕様を設定する必要がある
    otel.SetTextMapPropagator(
      ...
    )
}
// サーバー側マイクロサービス
// 後続のマイクロサービスにとってはクライアント側にもなる
func NewTracerProvider() {

    // 監視バックエンドが対応するトレースコンテキスト仕様を設定する必要がある
    otel.SetTextMapPropagator(
      ...
    )
}


06. Resource

Resourceとは

スパンに属性を設定する処理を持つ。


Resourceの種類

▼ HTTP/gRPCリクエストの場合

デフォルトで、様々な属性を持っている。

例えばGoであれば、go.opentelemetry.io/otel/resourceパッケージからコールできる。

有益な属性として、以下がある。

分散トレースの監視バックエンド (例:X-Ray) 上では、キー名はotel.resource.<属性名>になる。

{
  "service.name": "<マイクロサービス名>",
  "service.version": "<バージョンタグ>",
  "deployment.environment": "<実行環境名>",
  ...,
}

▼ DBクエリの場合

デフォルトで、様々な属性を持っている。

{
  "db.rows_affected": "<処理したレコード数>"
  "db.sql.table": "<テーブル名>",
  ...
}


07. Sampler

Samplerとは

スパンのサンプリング方式やサンプリング率を設定する処理を持つ。


パッケージ

項目 必要なパッケージ
Go go.opentelemetry.io/otel/sdk/traceパッケージからコールできる。


サンプリング方式

▼ 場所

サンプリングする場所によって、方式が異なる。

方式 説明
Head-based クライアント側で、スパンをサンプリングする。パフォーマンス (例:CPU、メモリ、スループット) に影響が低いが、エラーリクエストをサンプリングできない。
Tail-based サーバー側 (OpenTelemetry Collector) で、収集したスパンからサンプリングする (実際は全てをサンプリングすることが多い) 。パフォーマンス (例:CPU、メモリ、スループット) に影響があるが、エラーリクエストもトレーシングできる。

▼ クライアント側のサンプリング率

クライアント側でのサンプリング率を設定する。

Tail-based方式の場合、前提としてアプリケーションで全てのスパンをサンプリングするため、サンプリング率は100% (AlwaysOnまたはTraceIdRationBased=1.0) が推奨である。

ただ、負荷を抑える目的で、一定割合のアプリケーションでサンプリングすることもある。

設定 説明
AlwaysOn 全てのスパンをサンプリングする。本番環境で注意して使用する (非推奨というわけではない)。
AlwaysOff スパンをサンプリングしない。
TraceIdRationBased 指定した割合でスパンをランダムにサンプリングする。
ParentBased 親スパンの設定を継承する。TraceIdRationBasedと組み合わせて使用することが多い。

▼ サーバー側 (OpenTelemetry Collector) のサンプリング率

Tail-based方式の場合、OpenTelemetry Collectorでアプリケーションからの全てのスパンを収集した上で、Span Processorでサンプリング率を決める。

processors:
  attributes:
    actions:
      - key: collector
        value: otel-collector
        action: insert

  tail_sampling:
    decision_wait: 10s
    num_traces: 10
    policies: [{name: always-sample, type: always_sample}]


環境変数

▼ 一覧

OpenTelemetryの仕様では、あるべき環境変数が決まっている。

ただ、言語によって開発状況が異なり、使えない環境変数がある。

▼ 全体

指定するSamplerやパラメーターを環境変数で設定できる。

環境変数名 説明
OTEL_LOGS_EXPORTER ログのExporter名を設定する。
OTEL_METRICS_EXPORTER メトリクスのExporter名を設定する。執筆時点 (2024/02/06) では、otlp (HTTP/gRPC) 、prometheusnone、から設定できる。
OTEL_PROPAGATORS トレースコンテキスト仕様を設定する。
OTEL_SERVICE_NAME Resourceのservice.nameを設定する。
OTEL_RESOURCE_ATTRIBUTES Resourceの任意の属性を設定する。キーバリュー式 (key1=value1,key2=value2) で設定できる。
OTEL_TRACES_EXPORTER 分散トレースのExporter名を設定する。執筆時点 (2024/02/06) では、otlp (HTTP/gRPC) 、jaegerzipkinnone、から設定できる。
OTEL_TRACES_SAMPLER 使用するSamplerを設定する。
OTEL_TRACES_SAMPLER_ARG Samplerのパラメーター (例:サンプリング率) を設定する。

▼ Exporter

環境変数名 説明
OTEL_EXPORTER_OTLP_ENDPOINT
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
OTEL_EXPORTER_OTLP_LOGS_ENDPOINT
OTEL_EXPORTER_OTLP_HEADERS
OTEL_EXPORTER_OTLP_TRACES_HEADERS
OTEL_EXPORTER_OTLP_METRICS_HEADERS
OTEL_EXPORTER_OTLP_LOGS_HEADERS
OTEL_EXPORTER_OTLP_TIMEOUT
OTEL_EXPORTER_OTLP_TRACES_TIMEOUT
OTEL_EXPORTER_OTLP_METRICS_TIMEOUT
OTEL_EXPORTER_OTLP_LOGS_TIMEOUT
OTEL_EXPORTER_OTLP_PROTOCOL
OTEL_EXPORTER_OTLP_TRACES_PROTOCOL
OTEL_EXPORTER_OTLP_METRICS_PROTOCOL