dd-trace で OTLP Trace Export 機能を利用する

2026年07月21日

はじめに

Datadog の APM 向けライブラリである dd-trace が、OTLP Trace Export に対応しました。これにより、dd-trace で計装したアプリケーションから、Datadog のネイティブプロトコルだけでなく OTLP 形式でも Trace を送信できるようになりました。

これまでは、dd-trace から Datadog Agent 経由で Datadog に送信するか、OpenTelemetry Collector で受信する場合は Datadog のネイティブプロトコルに対応した datadogreceiver を利用する必要がありました。OTLP 形式での Trace 送信を利用すれば、OpenTelemetry Collector では標準の otlpreceiver だけで Trace を受信できます。また、OTLP をサポートする Observability Backend へ、Collector を介さずに dd-trace から直接 Trace を送信することも可能です。

本記事では、その一例として dd-trace-go を取り上げ、計装したアプリケーションから Jaeger へ Trace を OTLP 形式で直接送信する方法と、OTLP 形式での Trace 送信時の Head Based Sampling の挙動を紹介します。

前提

本記事では以下のバージョンのパッケージの使用を前提としています。

  • Go: 1.25.5
  • dd-trace-go: v2.9.1

また、本記事で扱うサンプルコードは以下のリポジトリで公開しています。

WARNING
dd-trace-go の OTLP Trace Export は 2026/07 現在 preview の機能です。将来のバージョンで挙動が変更される可能性があることに注意してください。また、dd-trace による OTLP 形式での Trace Export は、すべての言語でサポートされているわけではありません。利用する言語の対応状況を公式ドキュメントで確認してください。

dd-trace-go の OTLP Trace Export について

dd-trace-go はデフォルトでは Datadog のネイティブプロトコルで Trace を送信しますが、環境変数 OTEL_TRACES_EXPORTER=otlpDD_TRACE_OTEL_ENABLED=true を設定することで、Trace を OTLP 形式で送信するモードに切り替えることができます。

このモードでは、tracer が以下の環境変数を読み取って動作します。

環境変数役割
OTEL_TRACES_EXPORTER=otlpOpenTelemetry 仕様標準の環境変数。Trace の出力先プロトコルとして OTLP を指定する
DD_TRACE_OTEL_ENABLED=trueDatadog SDK 内で OpenTelemetry API / 機能の互換レイヤーを有効化する
OTEL_EXPORTER_OTLP_TRACES_ENDPOINT送信先のエンドポイント
OTEL_EXPORTER_OTLP_TRACES_HEADERS付与するヘッダー(認証情報など)。key=value をカンマ区切りで指定する

これらは OpenTelemetry 由来の環境変数に沿った命名であり、既存の OpenTelemetry の知識を活かしやすくなっています。ただし、dd-trace-go が対応している変数や挙動が OpenTelemetry SDK と完全に同一とは限らない点には注意が必要です。

送信は OTLP/HTTP の protobuf 形式(Content-Type: application/x-protobuf)で行われます。

また、Trace Context の伝搬方式は DD_TRACE_PROPAGATION_STYLE で制御します。本記事では W3C Trace Context を利用するため、DD_TRACE_PROPAGATION_STYLE=tracecontext を設定します。これにより、traceparent / tracestate ヘッダーを通じてサービス間で Trace Context が伝搬されます。

実際に送信する

ここでは例として、以下のようなコールチェーンを持つアプリケーションを用意し、Trace を OTLP 形式で送信してみます。送信先には、OTLP を直接受信できる Jaeger を利用します。

export_traces_from_dd-trace-go_00.png

各サービスの計装には、以下の dd-trace-go パッケージを利用し、OpenTelemetry SDK を追加せずに OTLP 形式で送信できることを検証します。

  • contrib/net/http/v2: HTTP サーバーの計装(inbound)
  • contrib/google.golang.org/grpc/v2: gRPC サーバー・クライアントの計装(Interceptor)
  • contrib/database/sql/v2: database/sql 経由の SQL クエリの計装

tracer の初期化

tracer の初期化は、通常の dd-trace-go の利用時と変わりません。Export モードやエンドポイント、伝搬方式はすべて環境変数で制御されるため、コード側では Service 名を指定するだけです。

internal/tracing/tracing.go
package tracing

import (
	"log/slog"

	"github.com/DataDog/dd-trace-go/v2/ddtrace/tracer"
)

func Start(service string) func() {
	// Export モード (OTLP か Datadog agent か)、エンドポイント、env、version、
	// 伝搬方式はすべて環境変数で制御されるため、ここでは何も設定しない。
	tracer.Start(
		tracer.WithService(service),
	)
	slog.Info("tracer started", "service", service)
	return func() {
		tracer.Stop()
		slog.Info("tracer stopped", "service", service)
	}
}

計装済みのアプリケーションに OTLP Trace Export を導入する際に、コードの変更範囲を小さくできる点は大きな利点です。

環境変数の設定

前述の環境変数を各サービスに設定します。ここでは Docker Compose で設定する例を示します。

compose.yaml
x-otel-env: &otel-env
  OTEL_TRACES_EXPORTER: otlp
  DD_TRACE_OTEL_ENABLED: "true"
  OTEL_EXPORTER_OTLP_TRACES_ENDPOINT: http://jaeger:4318/v1/traces
  # gRPC 間で W3C traceparent/tracestate を伝搬する
  DD_TRACE_PROPAGATION_STYLE: tracecontext
  DD_ENV: dev
  DD_VERSION: "0.1.0"

リクエストを送り Trace を確認する

docker compose up でアプリケーションと Jaeger を起動し、実際にリクエストを送って Trace を生成してみます。

sh
$ docker compose up --build -d
$ curl -sS -XPOST localhost:8080/orders -d '{"item":"book","quantity":3}'
$ curl -sS localhost:8080/orders
$ curl -sS localhost:8080/orders/1

Jaeger を開き、Service に http-gateway を選択して Trace を確認すると、以下のように 3 つのサービスと SQL クエリまでを含む Span が、W3C Trace Context によって 1 本の Trace として繋がっていることが確認できました。

export_traces_from_dd-trace-go_01.png

Head Based Sampling の挙動

OTLP Trace Export に切り替えた際に気になるのが、Sampling がこれまでと同じように動作するかという点です。ここでは dd-trace-go の Head Based Sampling が OTLP Trace Export でもそのまま動作することを確認してみます。

dd-trace-go は Head Sampler として動作します。すなわち、Sampling の判定は Trace の入り口となるサービス(head)で一度だけ行われ、その判定結果が W3C traceparent の sampled フラグを通じて下流のサービスへ伝搬されます。下流のサービスは伝搬されてきた親の判定を尊重する(parent-based)ため、Trace 全体で一貫した Sampling が行われます。

Trace ごとの判定は、以下のような順序で行われます。

  1. parent-based の判定で、親がすでに伝搬されている場合はそれを尊重する
  2. DD_TRACE_SAMPLING_RULES、次に DD_TRACE_SAMPLE_RATE を評価する
  3. いずれにも該当しない場合は fallback sampler を利用する

OTLP Trace Export では、明示的な Sampling 設定に該当しない場合の fallback sampler として parentbased_always_on が使われます。親の判定がすでに伝搬されていればそれを尊重し、親の判定がなければ Trace を rate 1.0 で保持します。

Head Based Sampling のレートは、入り口となるサービスの環境変数で制御します。

yaml
DD_TRACE_SAMPLE_RATE: "0.1"                                   # 10% を保持する
DD_TRACE_SAMPLING_RULES: '[{"service":"http-gateway","sample_rate":0.5}]'

この例では、http-gateway にマッチする Trace は DD_TRACE_SAMPLING_RULES により 50% 保持され、ルールにマッチしないサービスは DD_TRACE_SAMPLE_RATE により 10% 保持されます。

親の判定が尊重されることを確認する

parent-based の挙動を確認するため、入り口の http-gateway を KEEP(1.0)に、下流の grpc-service1 / grpc-service2 を DROP(0.0)に設定してみます。

compose.sampling.yaml
services:
  http-gateway:
    environment:
      DD_TRACE_SAMPLE_RATE: "1.0"
  grpc-service1:
    environment:
      DD_TRACE_SAMPLE_RATE: "0.0"
  grpc-service2:
    environment:
      DD_TRACE_SAMPLE_RATE: "0.0"

この overlay を適用して起動し、リクエストを送ってみます。

sh
$ docker compose -f compose.yaml -f compose.sampling.yaml up -d
$ curl -sS -XPOST localhost:8080/orders -d '{"item":"book","quantity":3}'

下流のサービスは自身のレートが 0.0(DROP)に設定されているにもかかわらず、Jaeger には 3 サービスすべてを含む Trace が保持されていることが確認できました。これは、下流のサービスが入り口の http-gateway の KEEP の判定を尊重しているためです。

逆に、値を反転させて入り口を DROP(0.0)、下流を KEEP(1.0)に設定すると、下流のレートが 1.0 であっても Trace は全体として Drop され、Jaeger には表示されなくなります。このとき、業務ロジック(DB への書き込み)自体は実行されています。これは、各サービスではなく入り口の判定が Trace 全体の Sampling を制御していることを示しています。

以上の検証結果をまとめると、以下のようになります。

gatewayservice1/2Jaeger の結果意味
A1.00.03 サービスを含む Trace が保持される親の KEEP が尊重される
B0.01.0新規 Trace は保持されない (DB 書き込みは実行済み)親の DROP が尊重される

OTLP Trace Export においても、dd-trace-go の Head Based Sampling (parent-based) がこれまでと同様に動作することが確認できました。

おわりに

本記事では、dd-trace-go v2 で計装したアプリケーションから Jaeger へ Trace を OTLP 形式で直接送信する方法を紹介しました。あわせて、OTLP Trace Export に切り替えても、dd-trace-go の Head Based Sampling がこれまでと同様に動作することを確認しました。

dd-trace が OTLP Trace Export に対応したことで、OpenTelemetry Collector を利用する場合も datadogreceiver を用意する必要がなくなり、標準の otlpreceiver だけで Trace を受信できるようになりました。これにより、既存の dd-trace-go の計装をほとんど変更することなく、Collector で Tail Based Sampling などの処理を行う構成を取りやすくなります。

さらに、今回の Jaeger のように OTLP をサポートする Observability Backend であれば、Collector を介さずに dd-trace から Trace を直接送信できます。既存の計装資産を活かしながら、Collector や Backend を要件に応じて選択できるようになり、テレメトリパイプラインの柔軟性が大きく高まったと感じました。

本記事において、異なっている説明や表現がありましたらご連絡ください。

参考