OpenTelemetry Java SDK¶
作成者: 刘锐
OpenTelemetry SDK を導入すると、ビジネスの中核ロジックを可観測化できます。たとえば、重要なビジネス処理にスパンを設定して、実際の動作の統計・追跡・分析を行ったり、ビジネス属性のメトリクスを設定したりできます。この方法にはある程度の侵入性があります。
起動コマンド¶
java -javaagent:../opentelemetry-javaagent/opentelemetry-javaagent.jar \
-Dotel.traces.exporter=otlp \
-Dotel.exporter.otlp.endpoint=http://192.168.91.11:4317 \
-Dotel.resource.attributes=service.name=demo,version=dev \
-Dotel.metrics.exporter=otlp \
-jar springboot-opentelemetry-otlp-server.jar --client=true
注意事項
起動パラメータの exporter の設定に応じて、SDK でも対応する exporter を追加する必要があります。追加しないと起動に失敗します。たとえば、起動パラメータで otlp と logging の 2 つの exporter を使用する場合、pom にそれらの依存関係を追加する必要があります。
SDK の依存関係を使用しない場合は、対応する調整は不要です。
依存関係の追加¶
<dependencies>
...
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-exporter-otlp</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-extension-annotations</artifactId>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-semconv</artifactId>
<version>1.21.0-alpha</version>
</dependency>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-sdk-extension-autoconfigure</artifactId>
<version>1.21.0-alpha</version>
</dependency>
...
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-bom</artifactId>
<version>1.21.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
SDK の作成¶
推奨しません
@Bean
public OpenTelemetry openTelemetry() {
return AutoConfiguredOpenTelemetrySdk.builder()
.setResultAsGlobal(false)
.build()
.getOpenTelemetrySdk();
}
上記の方法では AutoConfiguredOpenTelemetrySdk が再読み込みされる可能性があります。SDK はグローバルオブジェクト GlobalOpenTelemetry を提供しており、これを使用して OpenTelemetry オブジェクトを取得できます。
以下では主に GlobalOpenTelemetry.get() を使用して OpenTelemetry オブジェクトを取得します。
分散型トレーシング(Trace)¶
Tracer の作成¶
Tracer は主にスパンオブジェクトを取得・作成するために使用します。注意: Tracer は通常、設定を担当しません。これは TracerProvider の責務です。OpenTelemetry のインターフェースはデフォルトの TracerProvider 実装を提供しています。
openTelemetry() を使って Tracer オブジェクトを取得します。
スパンの作成¶
Info
Tracer 以外の API でスパンを作成することは許可されていません。
現在のスパンオブジェクトの取得¶
現在のスパンオブジェクトを取得し、属性やイベントなどを設定できます。
属性の作成¶
属性はスパンの属性であり、現在のスパンのラベルです。
Link Span の作成¶
1 つのスパンは、因果関係のある他の 1 つ以上のスパンにリンクできます。リンクはバッチ処理を表現するのに使用できます。たとえば、1 つのスパンの初期化が複数のスパンの初期化で構成され、各スパンがバッチ内の個々の入力項目を表す場合です。
Span child = tracer.spanBuilder(spanName)
.addLink(span(1))
.addLink(span(2))
.addLink(span(3))
.startSpan();
イベントの作成¶
スパンには、0 個以上のスパン属性を持つ名前付きイベントを注釈として付与できます。各イベントは key:value ペアであり、対応するタイムスタンプが自動的に付与されます。
注意
recordException は addEvent の特殊なバリアントであり、例外イベントを記録するために使用します。
ネストされたスパンの作成¶
setParent(parentSpan) を使用して親スパンを設定します。
void parent() {
Span parentSpan = tracer.spanBuilder("parent")
.startSpan();
childSpan(parentSpan);
parentSpan.end();
}
void childSpan(Span parentSpan) {
Span childSpan = tracer.spanBuilder("childSpan")
.setParent(parentSpan)
.startSpan();
// 処理を実行
childSpan.end();
}
Baggage の使用方法¶
Baggage はトレース全体に伝播でき、ユーザー ID やユーザー名などのグローバルなインストルメンテーションに適しており、ビジネスデータの追跡に役立ちます。
gateway メソッドで Baggage を設定
// Baggage の使用例、ここで設定
Baggage.current().toBuilder().put("app.username", "gateway").build().makeCurrent();
logger.info("gateway set baggage[app.username] value: gateway");
resource メソッドで Baggage を取得
// Baggage の使用例、ここで取得
String baggage = Baggage.current().getEntryValue("app.username");
logger.info("resource get baggage[app.username] value: {}", baggage);
既知の TraceId と SpanId から新しいスパンを構築する¶
Tracer のスパン構築には setParent(context) メソッドが用意されており、カスタムスパンに親スパンを設定できます。
setParent には Context パラメータを渡す必要があるため、コンテキストを構築する必要があります。
OpenTelemetry SDK は SpanContext を作成するための create メソッドのみを提供しており、TraceId と SpanId をカスタマイズできます。
SpanContext create(String traceIdHex, String spanIdHex, TraceFlags traceFlags, TraceState traceState)
SpanContext はスパンの一部として、シリアル化可能であり、分散コンテキストに沿って伝播される必要があります。SpanContext は不変です。
OpenTelemetry の SpanContext は W3C TraceContext 仕様に準拠しています。これには 2 つの識別子(TraceId と SpanId)、一連の汎用 TraceFlags、およびシステム固有の TraceState が含まれます。
-
TraceId有効なTraceIdは 16 バイトの配列で、少なくとも 1 バイトが非ゼロである必要があります。 -
SpanId有効なSpanIdは 8 バイトの配列で、少なくとも 1 バイトが非ゼロである必要があります。 -
TraceFlagsトレースの詳細を含みます。TraceFlagsはすべてのトレースに影響します(TraceFlagsとは異なります)。現在のバージョンで定義されているフラグは sampled のみです。 -
TraceStateトレース固有の識別データを保持し、KV ペアの配列で識別します。TraceStateにより、複数のトレーシングシステムが同じトレースに参加できます。完全な定義は W3C Trace Context specification を参照してください。
この API は SpanContext を作成するメソッドを実装する必要があります。これらのメソッドは SpanContext を作成する唯一の方法であるべきです。この機能は API で完全に実装され、オーバーライドできません。
ただし、SpanContext は Context ではないため、変換が必要です。
private Context withSpanContext(SpanContext spanContext, Context context) {
return context.with(Span.wrap(spanContext));
}
完全なコードは次のとおりです。
/***
* @Description 既知の TraceId と SpanId から新しいスパンを構築します。
* @Param [spanName, traceId, spanId]
* @return java.lang.String
**/
@GetMapping("/customSpanByTraceIdAndSpanId")
@ResponseBody
public String customSpanByTraceIdAndSpanId(String spanName,String traceId,String spanId){
assert StringUtils.isEmpty(spanName):"spanName は空にできません";
assert StringUtils.isEmpty(traceId):"traceId は空にできません";
assert StringUtils.isEmpty(spanId):"spanId は空にできません";
Context context =
withSpanContext(
SpanContext.create(
traceId, spanId, TraceFlags.getSampled(), TraceState.getDefault()),
Context.current());
Span span = tracer.spanBuilder(spanName)
.setParent(context)
.startSpan();
span.setAttribute("attribute.a2", "some value");
span.setAttribute("func","attr");
span.setAttribute("app","otel3");
span.end();
return buildTraceUrl(span.getSpanContext().getTraceId());
}
private Context withSpanContext(SpanContext spanContext, Context context) {
return context.with(Span.wrap(spanContext));
}
注意
現在のテスト実装では、リクエスト自体が新しいトレース情報を生成することに特に注意してください。新しく構築されたスパンは、渡されたパラメータに基づいて構築されます。
次のリンクにアクセスして結果を確認できます。
http://localhost:8080/customSpanByTraceIdAndSpanId?spanName=tSpan&traceId=24baeeddfbb35fceaf4c18e7cae58fe1&spanId=ff1955b4f0eacc4f
メトリクス(Metrics)¶
OpenTelemetry はメトリクス関連の操作を行う API も提供しています。
スパンはアプリケーションの詳細情報を提供しますが、生成されるデータはシステムの負荷に比例します。対照的に、メトリクスは個々の測定値を集約し、システム負荷の関数として一定のデータを生成します。集約は低レベルの問題を診断するために必要な詳細情報を欠いていますが、トレンドの特定を支援し、アプリケーションの実行時テレメトリを提供することでスパンを補完します。
メトリクス API はさまざまなインストゥルメントを定義しています。インストゥルメントは測定値を記録し、メトリクス SDK によって集約され、最終的にプロセス外にエクスポートされます。インストゥルメントには同期と非同期の 2 種類があります。同期インストゥルメントは測定結果を記録します。非同期インストゥルメントはコールバックを登録し、収集のたびに 1 回呼び出され、その時点の測定値を記録します。次のインストゥルメントを使用できます。
-
LongCounter/DoubleCounter: 正の値のみを記録します。同期・非同期の両方のオプションがあります。ネットワーク経由で送信されたバイト数など、何かをカウントするのに適しています。デフォルトでは、カウンターの測定値は常に増加する単調和として集約されます。
-
LongUpDownCounter/DoubleUpDownCounter: 正と負の両方の値を記録します。同期・非同期の両方のオプションがあります。キューのサイズなど、増減するものをカウントするのに適しています。デフォルトでは、アップダウンカウンターの測定値は非単調和として集約されます。
-
LongGauge/DoubleGauge: 非同期コールバックで瞬間的な値を測定します。CPU 使用率のパーセンテージなど、属性をまたいでマージできない値を記録するのに適しています。デフォルトでは、ゲージの測定値はゲージとして集約されます。
-
LongHistogram/DoubleHistogram: ヒストグラムの分布分析に最も有用な測定値を記録します。非同期オプションはありません。HTTP サーバーがリクエストの処理にかかった時間などを記録するのに適しています。デフォルトでは、ヒストグラムの測定値は明示的なバケットヒストグラムとして集約されます。
Meter オブジェクトの取得¶
API は Meter インターフェースを定義しています。このインターフェースは、一連のインストゥルメントコンストラクタと、アトミックにバッチで測定値を取得するためのツールで構成されています。Meter は MeterProvider の getMeter(name) メソッドを使用して新しいインスタンスを作成できます。MeterProvider は通常、シングルトンとして使用されることが期待されています。その実装は、グローバルに唯一の MeterProvider 実装として機能する必要があります。Meter オブジェクトを使用して、さまざまなタイプのメトリクスを構築できます。
ここでは MeterProvider の詳細を省略しています。主な理由は、OpenTelemetry のインターフェースが MeterProvider のデフォルトの noop 実装を提供しているためです。
ゲージタイプのメトリクスの構築¶
meter.gaugeBuilder("connections")
.setDescription("現在のSocket.io接続数")
.setUnit("1")
.buildWithCallback(
result -> {
System.out.println("metrics");
for (int i = 1; i < 4; i++) {
result.record(
i,
Attributes.of(
AttributeKey.stringKey("id"),
"a" + i));
}
});
buildWithCallback はコールバック関数であり、非同期 API をサポートし、必要に応じてメトリクスデータを収集するための追加ツールです。間隔を置いてデータを収集し、デフォルトでは 1分 に 1 回です。
関連ドキュメント¶
OpenTelemetry 分散型トレーシングデータの取り込み