OpenTelemetry Tracing の使用
このガイドでは、Quarkus アプリケーションで OpenTelemetry (OTel) を利用して、 インタラクティブな Web アプリケーションに分散トレースを提供する方法について説明します。
このドキュメントは、このコンポーネントとその他のオブザーバビリティ関連コンポーネントを特集した Quarkus のオブザーバビリティリファレンス ガイド の一部です。
|
要件
このガイドを完成させるには、以下が必要です:
-
約15分
-
IDE
-
JDK 17+がインストールされ、
JAVA_HOMEが適切に設定されていること -
Apache Maven 3.9.12
-
Docker と Docker Compose、または Podman 、および Docker Compose
-
使用したい場合は、 Quarkus CLI
-
ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること
ソリューション
次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。 ただし、すぐに完成した例に飛んでも構いません。
Clone the Git repository: git clone https://github.com/quarkusio/quarkus-quickstarts.git, or download an archive.
解決策は opentelemetry-quickstart ディレクトリー にあります。
Maven プロジェクトの作成
まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。
Windowsユーザーの場合:
-
cmdを使用する場合、(バックスラッシュ
\を使用せず、すべてを同じ行に書かないでください)。 -
Powershellを使用する場合は、
-Dパラメータを二重引用符で囲んでください。例:"-DprojectArtifactId=opentelemetry-quickstart"
このコマンドはMavenプロジェクトを生成し、 quarkus-opentelemetry エクステンションをインポートします。
このエクステンションには、デフォルトのOpenTelemetryサポート
と、 OTLPのgRPC spanエクスポーターが含まれています。
Quarkus プロジェクトがすでに設定されている場合、プロジェクトのベースディレクトリーで次のコマンドを実行することで、
quarkus-opentelemetry エクステンションをプロジェクトに追加できます:
quarkus extension add opentelemetry
./mvnw quarkus:add-extension -Dextensions='opentelemetry'
./gradlew addExtension --extensions='opentelemetry'
これにより、ビルドファイルに次の内容が追加されます。
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-opentelemetry</artifactId>
</dependency>
implementation("io.quarkus:quarkus-opentelemetry")
Jakarta REST リソースの調査
src/main/java/org/acme/opentelemetry/TracedResource.java のファイルを開くと、以下の内容が表示されます。
package org.acme.opentelemetry;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import org.jboss.logging.Logger;
@Path("/hello")
public class TracedResource {
private static final Logger LOG = Logger.getLogger(TracedResource.class);
@GET
@Produces(MediaType.TEXT_PLAIN)
public String hello() {
LOG.info("hello");
return "hello";
}
}
このアプリケーションには、トレースのためのコードが含まれていないことに注意してください。デフォルトでは、 このエンドポイントに送信されたリクエストは、コードの変更を必要とせずにトレースされます。
構成の作成
デフォルトでは、エクスポータはgRPCプロトコルとエンドポイント http://localhost:4317 を使用して、バッチでデータを送信します。
デフォルトのプロパティ値を変更する必要がある場合、 src/main/resources/application.properties ファイルを使用して、アプリケーション内でデフォルトの OTLP gRPC Exporter を設定する方法の例を以下に示します:
quarkus.application.name=myservice (1)
quarkus.otel.exporter.otlp.endpoint=http://localhost:4317 (2)
quarkus.otel.exporter.otlp.headers=authorization=Bearer my_secret (3)
quarkus.log.console.format=%d{HH:mm:ss} %-5p traceId=%X{traceId}, parentId=%X{parentId}, spanId=%X{spanId}, sampled=%X{sampled} [%c{2.}] (%t) %s%e%n (4)
# Alternative to the console log
quarkus.http.access-log.pattern="...traceId=%{X,traceId} spanId=%{X,spanId}" (5)
| 1 | アプリケーションから作成されたすべてのテレメトリには、 myservice アプリケーションによって作成されたことを示す OpenTelemetry Resource 属性が含まれます。
設定されていない場合、デフォルトでアーティファクト ID が設定されます。 |
| 2 | テレメトリを送信する gRPC エンドポイント。設定されていない場合、デフォルトは http://localhost:4317 . |
| 3 | 認証によく使われるオプションのgRPCヘッダー |
| 4 | ログメッセージにトレース情報を追加する。 |
| 5 | また、アクセスログにのみトレース情報を記載することもできます。この場合、コンソールログ形式の情報を省略する必要があります。 |
接続関連のプロパティには、シグナルに依存しない設定を提供しています。つまり、設定時にトレースとメトリクスの両方に同じプロパティを使用できます:
quarkus.otel.exporter.otlp.endpoint=http://localhost:4317
シグナルごとに異なる設定が必要な場合は、特定のプロパティを使用できます:
quarkus.otel.exporter.otlp.traces.endpoint=http://trace-uri:4317 (1)
quarkus.otel.exporter.otlp.metrics.endpoint=http://metrics-uri:4317 (2)
quarkus.otel.exporter.otlp.logs.endpoint=http://logs-uri:4317 (3)
| 1 | トレースエクスポーターのエンドポイント。 |
| 2 | メトリクスエクスポーターのエンドポイント。 |
| 3 | ログエクスポーターのエンドポイント。 |
スパンやログを終了時に直接エクスポートする必要がある場合(サーバーレス環境/アプリケーションなど)、このプロパティを true に設定できます。 これは、データのデフォルトのバッチ処理を置き換えます。
quarkus.otel.simple=true
アプリケーションの実行
まず、OpenTelemetry データを視覚化するシステムを起動する必要があります。 2 つの選択肢があります。
-
トレースとメトリクス用のオールインワン Grafana OTel LGTM システムの開始。
-
トレース専用の Jaeger システム。
Grafana OTel LGTM オプション
-
Grafana-OTel-LGTM のスタートガイド を参照してください。
これには、データを視覚化するための Grafana、ログを保存するための Loki、トレースを保存するための Tempo、メトリクスを保存するための Prometheus を含む Quarkus Dev Service が含まれています。また、データを受信するための OTel コレクターも提供されます。
トレースオプションを表示するための Jaeger。
OpenTelemetry Collector を設定して起動し、テレメトリーデータを受信、処理、およびエクスポートして、キャプチャされたトレースを Jaeger に表示できるようにします。
|
Jaeger-all-in-one は、Jaeger エージェント、OTel コレクター、クエリーサービス/UI を含みます。 分離型コレクターをインストールする必要はありません。トレースデータを直接 Jaeger に送ることができます (OTLP レシーバーを有効にした後、詳細はこちらの ブログエントリー などをご覧ください)。 |
docker-compose up -d OpenTelemetry CollectorとJaegerシステムを、以下の docker-compose.yml ファイルを介して起動します。
version: "2"
services:
# Jaeger
jaeger-all-in-one:
image: jaegertracing/all-in-one:latest
ports:
- "16686:16686" # Jaeger UI
- "14268:14268" # Receive legacy OpenTracing traces, optional
- "4317:4317" # OTLP gRPC receiver
- "4318:4318" # OTLP HTTP receiver
- "14250:14250" # Receive from external otel-collector, optional
environment:
- COLLECTOR_OTLP_ENABLED=true
オプションのポートは必要ないものは外しておいた方が良いです。
アプリケーションを起動します。
これでアプリケーションを実行する準備が整いました。トレーサーの設定に application.properties を使用している場合:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
または、JVM引数でOTLP gRPCエンドポイントを設定する場合:
quarkus dev -Djvm.args="-Dquarkus.otel.exporter.otlp.traces.endpoint=http://localhost:4317"
./mvnw quarkus:dev -Djvm.args="-Dquarkus.otel.exporter.otlp.traces.endpoint=http://localhost:4317"
./gradlew --console=plain quarkusDev -Djvm.args="-Dquarkus.otel.exporter.otlp.traces.endpoint=http://localhost:4317"
OpenTelemetry Collector、Jaegerシステム、アプリケーションが動作している状態で、提供されているエンドポイントにリクエストを出すことができます。
$ curl http://localhost:8080/hello
hello
最初のリクエストが送信された時点で、ログにトレース情報が表示されるようになります:
10:49:02 INFO traceId=, parentId=, spanId=, sampled= [io.quarkus] (main) Installed features: [cdi, opentelemetry, resteasy-client, resteasy, smallrye-context-propagation, vertx]
10:49:03 INFO traceId=17ceb8429b9f25b0b879fa1503259456, parentId=3125c8bee75b7ad6, spanId=58ce77c86dd23457, sampled=true [or.ac.op.TracedResource] (executor-thread-1) hello
10:49:03 INFO traceId=ad23acd6d9a4ed3d1de07866a52fa2df, parentId=, spanId=df13f5b45cf4d1e2, sampled=true [or.ac.op.TracedResource] (executor-thread-0) hello
その後、 Jaeger UI にアクセスしてトレース情報を確認します。
CTRL+C を押すか、 q と入力して、アプリケーションを停止してください。
JDBC
The JDBC instrumentation bundled with this extension will add a span for each JDBC queries done by your application.
As it uses a dedicated JDBC datasource wrapper, you must enable telemetry for your datasource with the quarkus.datasource.jdbc.telemetry property, as in the following example:
# enable tracing
quarkus.datasource.jdbc.telemetry=true
# configure datasource
quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/mydatabase
追加設定
一部のユースケースでは、OpenTelemetry のカスタム設定が必要になります。 これらのセクションでは、適切に設定するために必要なものについて概説します。
ID ジェネレーター
OpenTelemetry エクステンションでは、トレースおよびスパンの識別子を作成する際に、デフォルトでランダムな ID ジェネレーター を使用します。
ベンダー固有のプロトコルの中には、カスタム ID ジェネレーターを必要とするものがありますが、
プロデューサーを作成することで、デフォルトの ID ジェネレーターを上書きすることができます。OpenTelemetry エクステンションは、 IdGenerator CDI Bean を検出し、トレーサープロデューサーを設定する際にそれを使用します。
@Singleton
public class CustomConfiguration {
/** Creates a custom IdGenerator for OpenTelemetry */
@Produces
@Singleton
public IdGenerator idGenerator() {
return AwsXrayIdGenerator.getInstance();
}
}
プロパゲーター
OpenTelemetry は、 プロパゲーター を介して分野横断的な関心事を伝播し、状態を保存するための基になる Context を共有し、分散トランザクションの存続期間全体にわたってデータにアクセスします。
デフォルトでは、OpenTelemetryエクステンションは、W3C Trace Contextと W3C Baggage
プロパゲーターを有効にしていますが、OpenTelemetry 設定リファレンス で説明されている propagators 設定を行うことで、サポートされているOpenTelemetryプロパゲーターのいずれかを選択できます。
追加プロパゲーター
-
b3,b3multi,jaeger,ottraceのプロパゲーターは、プロジェクトに trace-propagators エクステンションを依存関係として追加する必要があります。
<dependency>
<groupId>io.opentelemetry</groupId>
<artifactId>opentelemetry-extension-trace-propagators</artifactId>
</dependency>
implementation("io.opentelemetry:opentelemetry-extension-trace-propagators")
-
xrayプロパゲーターは、お客様のプロジェクトに aws エクステンションを依存関係として追加することを必要とします。
<dependency>
<groupId>io.opentelemetry.contrib</groupId>
<artifactId>opentelemetry-aws-xray-propagator</artifactId>
</dependency>
implementation("io.opentelemetry.contrib:opentelemetry-aws-xray-propagator")
プロパゲーターをカスタマイズする
伝播ヘッダーをカスタマイズするには、 TextMapPropagatorCustomizer インターフェイスを実装します。たとえば、これを使用して、OpenTelemetry トレースヘッダーの伝播を制限し、機密性の高いデータがサードパーティーのシステムに送信されないようにすることができます。
/**
* /**
* Meant to be implemented by a CDI bean that provides arbitrary customization for the TextMapPropagator
* that are to be registered with OpenTelemetry
*/
public interface TextMapPropagatorCustomizer {
TextMapPropagator customize(Context context);
interface Context {
TextMapPropagator propagator();
ConfigProperties otelConfigProperties();
}
}
リソース
メインの OpenTelemetry ガイドリソース セクションを参照してください。
エンドユーザー属性
有効にすると、Quarkus は OpenTelemetry エンドユーザー属性を Span 属性として追加します。 この機能を有効にする前に、Quarkus Security エクステンションが存在し、設定されていることを確認してください。 Quarkus Security の詳細は、Quarkus Security の概要 を参照してください。
属性は、ベストエフォート方式で認証がすでに行われている場合にのみ追加されます。
End User 属性が Span 属性として追加されるかどうかは、Quarkus アプリケーションの認証および認可の設定によって異なります。
認証前にカスタム Span を作成した場合、Quarkus はそれらに End User 属性を追加できません。
Quarkus は、認証が完了した後にのみ、現在の Span に属性を追加できます。
カスタム Span に関するもう 1 つの重要な考慮事項は、Quarkus の SecurityIdentity を伝播するために使用されるアクティブな CDI リクエストコンテキストです。
原則として、カスタム Span が作成される前に CDI リクエストコンテキストがアクティブ化されている場合、Quarkus は End User 属性を追加できます。
quarkus.otel.traces.eusp.enabled=true (1)
quarkus.http.auth.proactive=true (2)
| 1 | End User 属性機能を有効にして、 SecurityIdentity プリンシパルとロールが Span 属性として追加されるようにします。
End User 属性は個人を特定できる情報であるため、この機能を有効にする前にエクスポートすることを確認してください。 |
| 2 | 必要に応じて、プロアクティブ認証を有効にします。 プロアクティブ認証を有効にすると、認証がより早く行われるため、最良の結果が得られます。 Quarkus アプリケーションでプロアクティブ認証を有効にする必要があるかどうかを判断する良い方法は、Quarkus の プロアクティブ認証 ガイドを読むことです。 |
| この機能は、カスタム Jakarta REST SecurityContexts が使用されている場合にはサポートされません。 |
サンプラー
サンプラー は、トレースを破棄するか転送するかを決定し、コレクターに送信される収集されたトレースの数を制限することで、ノイズを効果的に管理し、オーバーヘッドを削減します。
Quarkus には 組み込みサンプラー が装備されており、カスタムサンプラーを作成するオプションもあります。
組み込みサンプラーを使用するには、OpenTelemetry 設定リファレンス の説明にあるように、必要なサンプラーパラメーターを設定することで、設定できます。たとえば、トレースの 50% を保持するようにサンプラーを設定できます。
# build time property only:
quarkus.otel.traces.sampler=traceidratio
# Runtime property:
quarkus.otel.traces.sampler.arg=0.5
|
サンプラーの興味深いユースケースは、次の例のように、実行時にトレーシングのエクスポートをアクティブ化または非アクティブ化することです。
|
カスタムサンプラーを使用する必要がある場合、現在2種類の方法があります:
サンプラー CDI プロデューサー
サンプラー CDI プロデューサーを作成できます。Quarkus OpenTelemetry エクステンションは Sampler CDI Bean を検出し、Tracer を設定するときにそれを使用します。
@Singleton
public class CustomConfiguration {
/** Creates a custom sampler for OpenTelemetry */
@Produces
@Singleton
public Sampler sampler() {
return JaegerRemoteSampler.builder()
.setServiceName("my-service")
.build();
}
}
OTelサンプラーSPI
これにより、OTel 自動設定で使用可能な SPI フックが使用されます。 シンプルな Sampler クラスを作成できます。
public class CustomSPISampler implements Sampler {
@Override
public SamplingResult shouldSample(Context context,
String s,
String s1,
SpanKind spanKind,
Attributes attributes,
List<LinkData> list) {
// Do some sampling here
return Sampler.alwaysOn().shouldSample(context, s, s1, spanKind, attributes, list);
}
@Override
public String getDescription() {
return "custom-spi-sampler-description";
}
}
次に Sampler Provider を作成できます。
public class CustomSPISamplerProvider implements ConfigurableSamplerProvider {
@Override
public Sampler createSampler(ConfigProperties configProperties) {
return new CustomSPISampler();
}
@Override
public String getName() {
return "custom-spi-sampler";
}
}
resources/META-INF/services に io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSamplerProvider という名前の SPI ローダーテキストファイルを作成し、その中に CustomSPISamplerProvider クラスの完全修飾名を記述します。
次に、設定を有効にします。
quarkus.otel.traces.sampler=custom-spi-sampler
ご覧のとおり、CDI の方が操作がはるかに簡単です。
追加の計器
Quarkus エクステンションの中には、トレースが後続の実行に伝搬されることを保証するために追加のコードを必要とするものがあります。 これらのセクションでは、プロセスの境界を越えてトレースを伝搬するために必要なことを説明します。
このセクションで説明されている計装は、Quarkus でテストされており、標準モードとネイティブモードの両方で動作します。
CDI
CDI 対応の任意の Bean のメソッドに io.opentelemetry.instrumentation.annotations.WithSpan
アノテーションを付与すると、新しい Span が作成され、現在の Trace コンテキストとの必要な関係が確立されます。
CDI 対応の任意の Bean のメソッドに io.opentelemetry.instrumentation.annotations.AddingSpanAttributes アノテーションを付与すると、新しい Span は作成されませんが、アノテーションが付けられたメソッドのパラメーターが現在の Span の属性として追加されます。
メソッドに誤って @AddingSpanAttributes アノテーションと @WithSpan アノテーションが付けられている場合は、 @WithSpan アノテーションが優先されます。
メソッドのパラメーターに io.opentelemetry.instrumentation.annotations.SpanAttribute アノテーションを付与することで、
どのメソッドパラメーターが Span の一部となるべきかを示すことができます。また、パラメーター名はカスタマイズすることも可能です。
以下に例を示します。
@ApplicationScoped
class SpanBean {
@WithSpan
void span() {
}
@WithSpan("name")
void spanName() {
}
@WithSpan(kind = SERVER)
void spanKind() {
}
@WithSpan
void spanArgs(@SpanAttribute(value = "arg") String arg) {
}
@AddingSpanAttributes
void addArgumentToExistingSpan(@SpanAttribute(value = "arg") String arg) {
}
}
使用可能なOpenTelemetry CDI依存関係注入
MicroProfile Telemetry Tracing の仕様に基づき、Quarkus は以下のクラスの CDI 依存関係注入 をサポートしています。
-
io.opentelemetry.api.OpenTelemetry -
io.opentelemetry.api.trace.Tracer -
io.opentelemetry.api.trace.Span -
io.opentelemetry.api.baggage.Baggage
これらのクラスは、CDIが有効なBeanに注入することができます。例えば、 Tracer は、カスタムスパンを開始するために特に有用です。
@Inject
Tracer tracer;
...
public void tracedWork() {
Span span = tracer.spanBuilder("My custom span")
.setAttribute("attr", "attr.value")
.setParent(Context.current().with(Span.current()))
.setSpanKind(SpanKind.INTERNAL)
.startSpan();
// traced work
span.end();
}
Mutiny
リアクティブな型を返すメソッドには、 @WithSpan と @AddingSpanAttributes のアノテーションを付けて、新しいスパンを作成したり、現在のスパンに属性を追加したりすることもできます。
mutiny パイプライン内でスパンを手動で作成する必要がある場合は、 io.quarkus.opentelemetry.runtime.tracing.mutiny.MutinyTracingHelper の wrapWithSpan メソッドを使用してください。
例えば、次のようなパイプラインがあるとします:
Uni<String> uni = Uni.createFrom().item("hello")
//start trace here
.onItem().transform(item -> item + " world")
.onItem().transform(item -> item + "!")
//end trace here
.subscribe().with(
item -> System.out.println("Received: " + item),
failure -> System.out.println("Failed with " + failure)
);
このようにラップして下さい:
import static io.quarkus.opentelemetry.runtime.tracing.mutiny.MutinyTracingHelper.wrapWithSpan;
...
@Inject
Tracer tracer;
...
Context context = Context.current();
Uni<String> uni = Uni.createFrom().item("hello")
.transformToUni(m -> wrapWithSpan(tracer, Optional.of(context), "my-span-name",
Uni.createFrom().item(m)
.onItem().transform(item -> item + " world")
.onItem().transform(item -> item + "!")
))
.subscribe().with(
item -> System.out.println("Received: " + item),
failure -> System.out.println("Failed with " + failure)
);
マルチパイプラインの場合も同様です:
Multi.createFrom().items("Alice", "Bob", "Charlie")
.transformToMultiAndConcatenate(m -> TracingHelper.withTrace("my-span-name",
Multi.createFrom().item(m)
.onItem().transform(name -> "Hello " + name)
))
.subscribe().with(
item -> System.out.println("Received: " + item),
failure -> System.out.println("Failed with " + failure)
);
アイテムの順番を気にしないのであれば、 transformToMultiAndConcatenate の代わりに transformToMultiAndMerge を使うこともできます。
Quarkus Messaging - Kafka
Kafka の Quarkus Messaging エクステンションを使用する場合、 以下のようにして、スパンを Kafka レコードに伝播できます。
TracingMetadata tm = TracingMetadata.withPrevious(Context.current());
Message out = Message.of(...).withMetadata(tm);
上記は、生成されている Message に追加できる TracingMetadata オブジェクトを作成し、
OpenTelemetry Context を取得して、伝播のために現在のスパンを抽出しています。
Quarkus Security イベント
Quarkus は、セキュリティーイベント を OpenTelemetry Span イベントとしてエクスポートすることをサポートしています。
quarkus.otel.security-events.enabled=true (1)
| 1 | Quarkus Security イベントを OpenTelemetry Span イベントとしてエクスポートします。 |
Manual instrumentation
In cases were utilities or automatic instrumentation is not available, manual instrumentation can be used to generate spans, propagate the OpenTelemetry context and the Baggage.
|
Use Manual instrumentation if there are no other alternatives because it will require more maintenance work. |
Manual Span
On the following example we have a class with a method implementing a manual span with a custom attribute. The only business logic we are measuring there is the return "Hello from Quarkus REST"; statement.
@ApplicationScoped
public static class HelloSpan {
private final Tracer tracer;
// Instead of using @Inject on the tracer attribute
public HelloSpan(Tracer tracer) {
// The same as openTelemetry.getTracer("io.quarkus.opentelemetry");
this.tracer = tracer;
}
public String helloManualSpan() {
// Create a new span
Span span = tracer.spanBuilder("HelloBean.helloManualSpan").startSpan();
// Make sure span scope is closed
try (Scope scope = span.makeCurrent()) { (1)
// Add an attribute
span.setAttribute("myAttributeName", "myValue");
// Execute logic...
return "Hello from Quarkus REST";
} catch (Exception ignored) {
// Store potential exceptions.
span.recordException(ignored);
} finally {
// Whatever happens above, the span will be closed.
span.end();
}
return "failover message";
}
}
| 1 | It is essential that all the logic the span is measuring be well delimited by a scope. The scope is Autocloseable. |
Manual Context Propagation
When you send messages or requests with clients that don’t propagate the OpenTelemetry context you will have incomplete traces or broken traces, when the destination creates spans but because no context was propagated, the parent span is non-existent. This will cause more than one trace per request, which will cause problems.
Manual propagation can be implemented for those clients if there is a way to ship metadata containing the OpenTelemetry context with the message or request.
This is an advanced subject. For more details on context propagation please check the above section about プロパゲーター.
Next, we have an implementation example. The class contains a MAP_SETTER, a MAP_GETTER and 2 methods.
@ApplicationScoped
public static class HelloPropagation {
/**
* How data is stored
*/
private static final TextMapSetter<Map<String, String>> MAP_SETTER = new TextMapSetter<Map<String, String>>() {
@Override
public void set(@Nullable Map<String, String> carrier, String key, String value) {
carrier.put(key, value); (1)
}
};
/**
* How data is retrieved
*/
private static final TextMapGetter<Map<String, String>> MAP_GETTER = new TextMapGetter<>() {
@Override
public Iterable<String> keys(Map<String, String> carrier) {
return carrier == null ? emptyList() : carrier.keySet();
}
@Override
public @Nullable String get(@Nullable Map<String, String> carrier, String key) {
return carrier == null && key != null ? null : carrier.get(key); (2)
}
};
private final Tracer tracer;
private final OpenTelemetry openTelemetry;
// Instead of using @Inject on the tracer attribute
public HelloPropagation(Tracer tracer, OpenTelemetry openTelemetry) {
this.openTelemetry = openTelemetry;
// The same as openTelemetry.getTracer("io.quarkus.opentelemetry");
this.tracer = tracer;
}
public String createParentContext() { (3)
// Create the first manual span
Span parentSpan = tracer.spanBuilder("parent-span").startSpan();
try (Scope ignored = parentSpan.makeCurrent()) {
// inject into a temporary map and return the traceparent header value
// This can be stored in any data structure containing metadata in the payload to ship.
// As an example, for HTTP the headers, for JMS a message attribute
Map<String, String> tempCarrier = new HashMap<>();
// We will use The MAP_SETTER to place the header containing the OTel Context in the tempCarrier
openTelemetry.getPropagators()
.getTextMapPropagator()
.inject(Context.current(), tempCarrier, MAP_SETTER);
// W3C traceparent header key is "traceparent"
return tempCarrier.get("traceparent");
} finally {
parentSpan.end();
}
}
public void receiveAndUseContext(String traceparent) { (4)
// Rebuild a tempCarrier map that contains the "traceparent" header data
Map<String, String> tempCarrier = new HashMap<>();
tempCarrier.put("traceparent", traceparent);
// Extract context from the tempCarrier
Context extracted = openTelemetry.getPropagators()
.getTextMapPropagator()
.extract(Context.current(), tempCarrier, MAP_GETTER);
// Optionally check whether a parent span was found:
boolean hasParent = Span.fromContext(extracted).getSpanContext().isValid();
// Start a child span with the extracted context as explicit parent
Span child = tracer.spanBuilder("child-span")
.setParent(extracted)
.startSpan();
try (Scope scope = child.makeCurrent()) {
// Simulate work under child span
logger.infov("Child span started. Extracted parent valid? {0}", hasParent);
} finally {
child.end();
}
}
| 1 | The MAP_SETTER handles how to store data in the carrier map that will transport the context. |
| 2 | MAP_GETTER handles how to retrieve data from the carrier map transporting the context. |
| 3 | The createParentContext() method explains how to create an OpenTelemetry context. In this case we generate the default W3C Trace Context header: traceparent. |
| 4 | the receiveAndUseContext() method receives the contents of the traceparent header used to bootstrap a new OpenTelemetry context in the destination. |
Manual Baggage propagation
The Baggage can be used to send application specific data using the OpenTelemetry’s propagation mechanism.
In Quarkus, as documented in the 使用可能なOpenTelemetry CDI依存関係注入, Baggage can be injected to unwrap data coming with inbound requests but in more complex scenarios were data needs to be sent away in the Baggage manual instrumentation is required.
This is an advanced subject.
Using the HelloPropagation class from before we can add the following 2 methods, createBaggage() and receiveAndUseBaggage().
public Map<String, String> createBaggage(String withValue) {
// Baggage can be used to send Application specific data using the OpenTelemetry propagation mechanism
Baggage baggage = Baggage.builder()
.put("baggage_key", withValue, BaggageEntryMetadata.empty())
.build();
Context context = baggage.storeInContext(Context.current());
// We use this to store the header data to ship out.
Map<String, String> tempCarrier = new HashMap<>();
// We will use The MAP_SETTER to place the header containing the OTel Context in the tempCarrier
try (Scope ignored = context.makeCurrent()) {
openTelemetry.getPropagators()
.getTextMapPropagator()
.inject(Context.current(), tempCarrier, MAP_SETTER);
}
return tempCarrier;
}
public String receiveAndUseBaggage(Map<String, String> headers) {
// Extract context from the tempCarrier
Context extracted = openTelemetry.getPropagators()
.getTextMapPropagator()
.extract(Context.current(), headers, MAP_GETTER);
// Retrieve the baggage contents
Baggage baggage = Baggage.fromContext(extracted);
BaggageEntry baggageKey = baggage.asMap().get("baggage_key");
return baggageKey != null ? baggageKey.getValue() : null;
}
エクスポーター
メインの OpenTelemetry ガイドのエクスポーター セクションを参照してください。
OpenTelemetry Tracing で計装された Quarkus コアエクステンション
-
-
AMQP 1.0
-
RabbitMQ
-
Kafka
-
Pulsar
-
-
quarkus-vertx (httpリクエスト)
自動トレースの一部を無効にする
quarkus.otel.instrument.* プロパティーを false に設定することで、自動トレーシング計装部分を無効にすることができます。
例:
quarkus.otel.instrument.grpc=false
quarkus.otel.instrument.messaging=false
quarkus.otel.instrument.resteasy-client=false
quarkus.otel.instrument.rest=false
quarkus.otel.instrument.resteasy=false
OpenTelemetry 設定リファレンス
メインの OpenTelemetry ガイド設定 リファレンスを参照してください。