The English version of quarkus.io is the official project site. Translated sites are community supported on a best-effort basis.
このページを編集

OpenTelemetry Tracing の使用

このガイドでは、Quarkus アプリケーションで OpenTelemetry (OTel) を利用して、 インタラクティブな Web アプリケーションに分散トレースを提供する方法について説明します。

このドキュメントは、このコンポーネントとその他のオブザーバビリティ関連コンポーネントを特集した Quarkus のオブザーバビリティリファレンス ガイド の一部です。

  • OpenTelemetry ガイド には、信号に依存しない、OpenTelemetry エクステンションに関する情報が記載されています。

  • OpenTelemetry Metrics の詳細情報を検索する場合は、OpenTelemetry Metrics ガイド を参照してください。

要件

このガイドを完成させるには、以下が必要です:

  • 約15分

  • IDE

  • JDK 17+がインストールされ、 JAVA_HOME が適切に設定されていること

  • Apache Maven 3.9.15

  • Docker と Docker Compose、または Podman 、および Docker Compose

  • 使用したい場合は、 Quarkus CLI

  • ネイティブ実行可能ファイルをビルドしたい場合、MandrelまたはGraalVM(あるいはネイティブなコンテナビルドを使用する場合はDocker)をインストールし、 適切に設定していること

アーキテクチャー

このガイドでは、分散トレースを実証するための簡単なRESTアプリケーションを作成します。

ソリューション

次の章で紹介する手順に沿って、ステップを踏んでアプリを作成することをお勧めします。 ただし、すぐに完成した例に飛んでも構いません。

Clone the Git repository: git clone https://github.com/quarkusio/quarkus-quickstarts.git, or download an archive.

解決策は opentelemetry-quickstart ディレクトリー にあります。

Maven プロジェクトの作成

まず、新しいプロジェクトが必要です。以下のコマンドで新規プロジェクトを作成します。

コマンドラインインタフェース
quarkus create app org.acme:opentelemetry-quickstart \
    --extension='rest,quarkus-opentelemetry' \
    --no-code
cd opentelemetry-quickstart

Gradleプロジェクトを作成するには、 --gradle または --gradle-kotlin-dsl オプションを追加します。

Quarkus CLIのインストールと使用方法の詳細については、 Quarkus CLI ガイドを参照してください。

Maven
mvn io.quarkus.platform:quarkus-maven-plugin:3.35.2:create \
    -DprojectGroupId=org.acme \
    -DprojectArtifactId=opentelemetry-quickstart \
    -Dextensions='rest,quarkus-opentelemetry' \
    -DnoCode
cd opentelemetry-quickstart

Gradleプロジェクトを作成するには、 -DbuildTool=gradle または -DbuildTool=gradle-kotlin-dsl オプションを追加します。

Windowsユーザーの場合:

  • cmdを使用する場合、(バックスラッシュ \ を使用せず、すべてを同じ行に書かないでください)。

  • Powershellを使用する場合は、 -D パラメータを二重引用符で囲んでください。例: "-DprojectArtifactId=opentelemetry-quickstart"

このコマンドはMavenプロジェクトを生成し、 quarkus-opentelemetry エクステンションをインポートします。 このエクステンションには、デフォルトのOpenTelemetryサポート と、 OTLPのgRPC spanエクスポーターが含まれています。

Quarkus プロジェクトがすでに設定されている場合、プロジェクトのベースディレクトリーで次のコマンドを実行することで、 quarkus-opentelemetry エクステンションをプロジェクトに追加できます:

コマンドラインインタフェース
quarkus extension add opentelemetry
Maven
./mvnw quarkus:add-extension -Dextensions='opentelemetry'
Gradle
./gradlew addExtension --extensions='opentelemetry'

これにより、ビルドファイルに次の内容が追加されます。

pom.xml
<dependency>
    <groupId>io.quarkus</groupId>
    <artifactId>quarkus-opentelemetry</artifactId>
</dependency>
build.gradle
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

See data

Before starting the app, please set up the system to visualize the OpenTelemetry data. We have several options:

  • Start an all-in-one Grafana OTel LGTM Dev Service for traces, logs and metrics

  • Jaeger system just for traces

  • Logging エクスポーター

Grafana OTel LGTM オプション

A Dev Service that will receive your app’s telemetry. Only the dependency is needed.

This features a Quarkus Dev Service including a Grafana for visualizing data, Loki to store logs, Tempo to store traces and Prometheus to store metrics. Also provides an OTel collector to receive the data.

Jaeger v2 to see traces option

Jaeger V2 is a tool to visualize spans, and it’s based on OpenTelemetry collector. There is no need to install a separate collector. More details are available in this blog post.

Start the OpenTelemetry Collector and Jaeger system via the following Docker command:

docker run -it \
  -p 16686:16686 \
  -p 4317:4317 \
  -p 4318:4318 \
jaegertracing/jaeger:latest

以下の場合:

Port Purpose

16686

Jaeger UI

4317

OpenTelemetry collector. OTLP protobuf gRPC receiver.

4318

OpenTelemetry collector. OTLP protobuf HTTP receiver.

Other ports are available in the Jaeger documentation.

Logging エクスポーター

You can output all traces to the console by setting the exporter to logging in the application.properties file:

quarkus.otel.traces.exporter=logging

Usually there is no need to set the quarkus.otel.traces.exporter property. The default value is cdi and is managed by Quarkus.

This dependency must be added to the project:

<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-exporter-logging</artifactId>
</dependency>

アプリケーションの実行

アプリケーションを起動します。

これでアプリケーションを実行する準備が整いました。トレーサーの設定に application.properties を使用している場合:

コマンドラインインタフェース
quarkus dev
Maven
./mvnw quarkus:dev
Gradle
./gradlew --console=plain quarkusDev

または、JVM引数でOTLP gRPCエンドポイントを設定する場合:

コマンドラインインタフェース
quarkus dev -Djvm.args="-Dquarkus.otel.exporter.otlp.traces.endpoint=http://localhost:4317"
Maven
./mvnw quarkus:dev -Djvm.args="-Dquarkus.otel.exporter.otlp.traces.endpoint=http://localhost:4317"
Gradle
./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

このエクステンションにバンドルされている JDBC 計装 は、アプリケーションによって行われる各 JDBC クエリーに対してスパンを追加します。

専用の JDBC データソースラッパーを使用するため、以下の例のように quarkus.datasource.jdbc.telemetry プロパティーを使用してデータソースのテレメトリーを有効にする必要があります。

# enable tracing
quarkus.datasource.jdbc.telemetry=true

# configure datasource
quarkus.datasource.db-kind=postgresql
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/mydatabase

For more details check the Datasource tracing documentation.

追加設定

一部のユースケースでは、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 ContextW3C Baggage プロパゲーターを有効にしていますが、OpenTelemetry 設定リファレンス で説明されている propagators 設定を行うことで、サポートされているOpenTelemetryプロパゲーターのいずれかを選択できます。

追加プロパゲーター

  • b3, b3multi, jaeger, ottrace のプロパゲーターは、プロジェクトに trace-propagators エクステンションを依存関係として追加する必要があります。

pom.xml
<dependency>
    <groupId>io.opentelemetry</groupId>
    <artifactId>opentelemetry-extension-trace-propagators</artifactId>
</dependency>
build.gradle
implementation("io.opentelemetry:opentelemetry-extension-trace-propagators")
  • xray プロパゲーターは、お客様のプロジェクトに aws エクステンションを依存関係として追加することを必要とします。

pom.xml
<dependency>
    <groupId>io.opentelemetry.contrib</groupId>
    <artifactId>opentelemetry-aws-xray-propagator</artifactId>
</dependency>
build.gradle
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

サンプラーの興味深いユースケースは、次の例のように、実行時にトレーシングのエクスポートをアクティブ化または非アクティブ化することです。

# build time property only:
quarkus.otel.traces.sampler=traceidratio
# On (default). All traces are exported:
quarkus.otel.traces.sampler.arg=1.0
# Off. No traces are exported:
quarkus.otel.traces.sampler.arg=0.0

カスタムサンプラーを使用する必要がある場合、現在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/servicesio.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.MutinyTracingHelperwrapWithSpan メソッドを使用してください。

例えば、次のようなパイプラインがあるとします:

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 イベントとしてエクスポートします。

手動計装

ユーティリティーまたは自動計装が利用できない場合、手動計装を使用してスパンを生成し、OpenTelemetry コンテキストと Baggage を伝播することができます。

より多くのメンテナンス作業が必要になるため、他に選択肢がない場合にのみ手動計装を使用してください。

手動スパン

以下の例では、カスタム属性を持つ手動スパンを実装するメソッドを持つクラスがあります。そこで計測している唯一のビジネスロジックは、 return "Hello from Quarkus REST"; ステートメントです。

@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 スパンが測定するすべてのロジックが、スコープによって明確に区切られていることが不可欠です。そのスコープは Autocloseable です。

手動コンテキスト伝播

OpenTelemetry コンテキストを伝播しないクライアントでメッセージまたはリクエストを送信すると、宛先がスパンを作成しても、コンテキストが伝播されなかったため、親スパンが存在しない場合に不完全なトレースまたは壊れたトレースが発生します。これにより、リクエストごとに複数のトレースが発生し、問題を引き起こします。

OpenTelemetry コンテキストを含むメタデータをメッセージまたはリクエストとともに送信する方法がある場合、これらのクライアントに対して手動伝播を実装できます。

これは高度なトピックです。コンテキスト伝播の詳細については、プロパゲーター の上記のセクションを参照してください。

次に、実装例を示します。このクラスには、MAP_SETTERMAP_GETTER、および 2 つのメソッドが含まれています。

@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 MAP_SETTER は、コンテキストを転送するキャリアーマップにデータを格納する方法を処理します。
2 MAP_GETTER は、コンテキストを転送するキャリアーマップからデータを取得する方法を処理します。
3 createParentContext() メソッドは、OpenTelemetry コンテキストを作成する方法を説明しています。この場合、デフォルトの W3C Trace Context ヘッダーである traceparent を生成します。
4 receiveAndUseContext() メソッドは、宛先で新しい OpenTelemetry コンテキストをブートストラップするために使用される traceparent ヘッダーの内容を受け取ります。

手動 Baggage 伝播

Baggage を使用して、OpenTelemetry の伝播メカニズムを使用してアプリケーション固有のデータを送信できます。

Quarkus では、使用可能なOpenTelemetry CDI依存関係注入 に記載されているように、Baggage は受信リクエストとともに来るデータをアンラップするために注入できますが、Baggage にデータを送る必要があるより複雑なシナリオでは、手動計装が必要です。

これは高度なトピックです

前述の HelloPropagation クラスを使用して、次の 2 つのメソッド createBaggage()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 コアエクステンション

自動トレースの一部を無効にする

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

アプリケーションエンドポイントの特定のトレースを無効にする

quarkus.otel.traces.suppress-application-uris プロパティーを使用して、特定のエンドポイントをトレース対象から除外できます。

設定例

# application.properties
quarkus.otel.traces.suppress-application-uris=trace,ping,people*

この設定では、以下が実行されます。

  • /trace URI のトレーシングを無効にします。

  • /ping URI のトレーシングを無効にします。

  • /people URI と、 /people/1/people/1/cars などのすべてのサブパスのトレーシングを無効にします。

quarkus.http.root-path を使用している場合は、設定にルートパスを含めるようにしてください。

OpenTelemetry 設定リファレンス

メインの OpenTelemetry ガイド設定 リファレンスを参照してください。

関連コンテンツ