OpenTelemetryは、多くのライブラリに計装ライブラリを提供していて、これは通常、ライブラリフックやモンキーパッチライブラリコードを通して行われます。
OpenTelemetry を使ったネイティブのライブラリ計装は、ユーザにより良いオブザーバビリティと開発者体験を提供し、ライブラリがフックを公開して、ドキュメントを書く必要性を取り除きます。
ウェブフレームワーク、RPCクライアント、データベース、メッセージングクライアント、インフラなどをカバーする利用可能なセマンティック規約をチェックしてください!
もしあなたのライブラリがそのようなものであるなら、規約にしたがってください。 規約は主要な情報源であり、どの情報がスパンに含まれるべきかを教えてくれます。 規約は、計装に一貫性を持たせます。テレメトリーに携わるユーザーは、ライブラリの仕様を学ぶ必要がありませんし、オブザーバビリティベンダーは、さまざまなテクノロジー(たとえば、データベースやメッセージングシステム)に対応した体験を構築できます。 ライブラリが規約にしたがえば、ユーザーの入力や設定なしで、多くのシナリオがそのまま有効になります。
セマンティック規約は常に進化しており、常に新しいものが追加されています。 もしあなたのライブラリにないものがあれば、追加することを検討してください。 スパン名には特に注意してください。 意味のある名前を使うように努め、定義する際にはカーディナリティを考慮してください。
[schema_url
](/docs/specs/otel/schemas/#schema-url)属性があり、どのバージョンのセマンティック規約が使用されているかを記録できます。
可能であれば、この属性を設定してください。
何かフィードバックがあったり、新しい規約を追加したい場合は、ぜひ貢献してください!
CNCFのOpenTelemetryに関する計装チャンネル( #otel-instrumentation
)や仕様のレポジトリから始めるとよいでしょう!
ライブラリの利用者の視点からライブラリのことを考え、利用者がライブラリの動作やアクティビティについて何を知りたいと思うかを考えてみてください。 ライブラリのメンテナーであるあなたは内部構造を知っていますが、ユーザーはライブラリの内部構造にはあまり興味を持たず、自分のアプリケーションの機能に興味を持つでしょう。 ライブラリの使用状況を分析する上でどのような情報が役に立つかを考え、そのデータをモデル化する適切な方法を考えましょう。 考慮すべき点は以下の通りです。
たとえば、ライブラリがデータベースへのリクエストを行っている場合、データベースへの論理的なリクエストに対してのみスパンを作成します。 ネットワークを介した物理的なリクエストは、その機能を実装するライブラリ内で計装する必要があります。 また、オブジェクトやデータのシリアライズのような他のアクティビティは、追加スパンとしてではなく、スパンイベントとして捕捉することをおすすめします。
スパン属性を設定するときは、セマンティック規約にしたがってください。
いくつかのライブラリは、ネットワーク呼び出しをラップするシンクライアントです。 OpenTelemetryに、RPCクライアント用の計装ライブラリがある可能性があります(レジストリをチェックしてください)。 この場合、ラッパーライブラリの計装は必要ないかもしれません。 一般的なガイドラインとして、ライブラリの計装は、そのライブラリ自身のレベルでのみ行ってください。
次のような場合は計装しないでください。
迷ったら、計装はやめましょう。後で必要性を感じたときにいつでもできます。
もし、計装しないことを選択した場合でも、内部のRPCクライアントインスタンスに OpenTelemetryハンドラーを設定する方法を提供することは有用でしょう。 これは、完全な自動計装をサポートしていない言語では必須ですが、その他の言語でも有用です。
この文書の残りの部分では、計装を行うことを決定した場合、何をどのように計装するのかについて手引きを示します。
最初のステップは、OpenTelemetry APIパッケージへ依存することです。
OpenTelemetryには2つの主要なモジュールであるAPIとSDKがあります。 OpenTelemetry API は、抽象化と動作しない実装のセットです。 アプリケーションが OpenTelemetry SDKをインポートしない限り、あなたの計装は何もせず、アプリケーションのパフォーマンスに影響を与えません。
ライブラリはOpenTelemetry APIのみを使用すべきです。
新しい依存関係を追加することを心配するのは当然かもしれませんが、依存地獄を最小限に抑える方法を決めるのに役立ついくつかの考慮事項を紹介しましょう。
OpenTelemetry Trace APIは2021年初めに安定版に達しました。このAPIはSemantic Versioning 2.0にしがたっていて、開発チームはAPIの安定性を真剣に受け止めています。
依存する場合は、もっとも早い安定版の OpenTelemetry API (1.0.*)を使用し、新機能を使用する必要がない限り、アップデートは避けてください。
あなたの計装が安定するまでの間、それを別のパッケージとしてリリースすることを検討してください。 あなたのレポジトリに置いておくこともできますし、OpenTelemetryに追加 して、他の計装パッケージと一緒にリリースすることもできます。
セマンティック規約は安定していますが、徐々に発展しています。 機能的な問題は発生しませんが、ときどき、計装をアップデートする必要があるかもしれません。 プレビュープラグインか、OpenTelemetry contrib リポジトリにそれを置くことで、ユーザの変更を壊すことなく、規約を最新に保つことができるかもしれません。
すべてのアプリケーションの設定は、Tracer API を通してライブラリから隠蔽されます。
ライブラリは、アプリケーションに TracerProvider
のインスタンスを渡して依存性注入とテストの容易さを促進したり、グローバルの TracerProvider
から取得したりできます。
OpenTelemetry 言語の実装は、インスタンスの受け渡しやグローバルへのアクセスについて、慣用的なものに基づいて好みが異なるかもしれません。
トレーサーを入手する際、ライブラリ(またはトレーシングプラグイン)の名前とバージョンを指定してください。 これらはテレメトリーに表示され、ユーザーがテレメトリーを処理してフィルタリングし、それがどこから来たのかを理解し、計装の問題をデバッグ/報告するのに役立ちます。
パブリックAPI呼び出し用に作成されたスパンによって、ユーザーはテレメトリーをアプリケーションコードにマッピングし、ライブラリ呼び出しの期間と結果を理解できます。 トレースすべき呼び出しは次のとおりです。
計装の例:
private static Tracer tracer = getTracer(TracerProvider.noop());
public static void setTracerProvider(TracerProvider tracerProvider) {
tracer = getTracer(tracerProvider);
}
private static Tracer getTracer(TracerProvider tracerProvider) {
return tracerProvider.getTracer("demo-db-client", "0.1.0-beta1");
}
private Response selectWithTracing(Query query) {
// スパンの名前と属性に関する手引きについては、規約をチェックすること
Span span = tracer.spanBuilder(String.format("SELECT %s.%s", dbName, collectionName))
.setSpanKind(SpanKind.CLIENT)
.setAttribute("db.name", dbName)
...
.startSpan();
// スパンをアクティブにし、ログとネストスパンの関連付けを可能にする
try (Scope unused = span.makeCurrent()) {
Response response = query.runWithRetries();
if (response.isSuccessful()) {
span.setStatus(StatusCode.OK);
}
if (span.isRecording()) {
// レスポンスコードやその他の情報をレスポンス属性に入力する
}
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR, e.getClass().getSimpleName());
throw e;
} finally {
span.end();
}
}
属性を入力するための規約に従ってください! 該当するものがない場合は、一般的な規約をチェックしてください。
ネットワーク呼び出しは通常、対応するクライアントの実装を通して、OpenTelemetry 自動計装でトレースされます。
OpenTelemetryがネットワーククライアントのトレースをサポートしていない場合、最善の判断をしてください。
OpenTelemetryがすでにネットワーク呼び出しのトレースをサポートしているのであれば、おそらく、それを複製する必要はないでしょう。 以下のように例外もあります。
警告:重複を避けるための一般的なソリューションは現在作成中です 🚧
トレースは、アプリが発するシグナルの一種です。 イベント(またはログ)とトレースは、互いに補完し合うものであり、重複するものではありません。 冗長性を持たせる必要がある場合は、トレースよりもログの方が適しています。
あなたのアプリは、すでにロギングか、似たようなモジュールを使っている可能性があります。 あなたのモジュールは、すでに OpenTelemetry と統合されているかもしれません。それを調べるには、レジストリ を参照してください。 統合は通常、すべてのログにアクティブなトレースコンテキストを埋め込むことで、ユーザがそれらを関連付けられるようになります。
あなたの言語とエコシステムが共通のロギングサポートを持っていない場合、スパンイベントを使って追加のアプリの詳細を共有します。 属性も追加したい場合は、イベントの方が便利かもしれません。
経験則として、詳細データにはスパンではなくイベントまたはログを使用しましょう。 常に、計装が作成したスパンインスタンスにイベントを添付してください。 アクティブスパンの使用は、それが何を参照しているのか制御できないため、できる限り避けてください。
もしあなたがライブラリやサービス、たとえばウェブフレームワークやメッセージングコンシューマーなどの上流の呼び出しを受信するような仕事をしているなら、受信するリクエスト/メッセージからコンテキストを抽出する必要があります。
OpenTelemetryは Propagator
APIを提供していて、これは特定の伝播基準を隠して、トレースされた Context
をワイヤーから読み取ります。
単一のレスポンスの場合、ワイヤー上のコンテキストは1つだけです。これはライブラリが作成する新しいスパンの親になります。
スパンを作成した後、スパンをアクティブにすることで、新しいトレースコンテキストをアプリケーションコード(コールバックまたはハンドラー)に渡す必要があります。
// コンテキストを抽出する
Context extractedContext = propagator.extract(Context.current(), httpExchange, getter);
Span span = tracer.spanBuilder("receive")
.setSpanKind(SpanKind.SERVER)
.setParent(extractedContext)
.startSpan();
// スパンをアクティブにし、ネストされたテレメトリーが相関するようにする
try (Scope unused = span.makeCurrent()) {
userCode();
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR);
throw e;
} finally {
span.end();
}
ここにJava でのコンテキスト抽出の例 の完全なものがあります。
メッセージングシステムの場合、一度に複数のメッセージを受け取ることがあります。 受信したメッセージは、作成したスパンの リンク になります。 詳しくはメッセージング規約を参照してください(警告:メッセージング規約は策定中 🚧 です)。
外部呼び出しを行う場合、通常はコンテキストを下流のサービスに伝搬させたくなります。
この場合、外部呼び出しをトレースする新しいスパンを作成し、Propagator
API を使ってメッセージにコンテキストを注入します。
非同期処理用のメッセージを作成する場合など、コンテキストを注入したいケースは他にもあるかもしれません。
Span span = tracer.spanBuilder("send")
.setSpanKind(SpanKind.CLIENT)
.startSpan();
// スパンをアクティブにすることで、ネスト化されたテレメトリを相関させる
// ネットワークコールでも、スパン、ログ、イベントのネスト化されたレイヤーがあるかもしれない
try (Scope unused = span.makeCurrent()) {
// コンテキストを注入
propagator.inject(Context.current(), transportLayer, setter);
send();
} catch (Exception e) {
span.recordException(e);
span.setStatus(StatusCode.ERROR);
throw e;
} finally {
span.end();
}
Javaにおけるコンテキスト注入の例の全体はこちらを参照してください。
下記のように例外もあるかもしれません。
OpenTelemetryレジストリにあなたの計装ライブラリを追加してください。
OpenTelemetryのAPIは、アプリケーションにSDKがない場合、no-opで、非常にパフォーマンスが良いです。 OpenTelemetry SDK が設定されると、バインドされたリソースを消費します。
実際のアプリケーション、特に大規模なものでは、ヘッドベースのサンプリングが頻繁に設定されます。 サンプリングアウトされたスパンは安価であり、属性の入力中に余分な割り当てや高価な計算を避けるために、スパンが記録されているかどうかをチェックできます。
// サンプリングに重要な属性がある場合は、作成時に提供する必要がある
Span span = tracer.spanBuilder(String.format("SELECT %s.%s", dbName, collectionName))
.setSpanKind(SpanKind.CLIENT)
.setAttribute("db.name", dbName)
...
.startSpan();
// スパンが記録される場合は、他の属性、特に計算コストのかかる属性を追加する必要がある
if (span.isRecording()) {
span.setAttribute("db.statement", sanitize(query.statement()))
}
OpenTelemetry APIは、実行時に寛容です。 無効な引数では失敗せず、決して例外をスローせずに飲み込みます。 このようにして、計装の問題がアプリケーションロジックに影響を与えないようにします。 OpenTelemetry が実行時に隠す問題に気づくために、計装をテストしてください。
OpenTelemetry にはさまざまな自動計装があるので、あなたの計装が他のテレメトリー(受信リクエスト、送信リクエスト、ログなど)とどのように相互作用するかを試してみるのは便利です。 計装を試すときは、一般的なフレームワークとライブラリを使い、すべてのトレースを有効にした典型的なアプリケーションを使ってください。 あなたのライブラリと似たライブラリがどのように表示されるかをチェックしてください。
ユニットテストでは、通常、SpanProcessor
とSpanExporter
をモックまたはフェイクできます。
@Test
public void checkInstrumentation() {
SpanExporter exporter = new TestExporter();
Tracer tracer = OpenTelemetrySdk.builder()
.setTracerProvider(SdkTracerProvider.builder()
.addSpanProcessor(SimpleSpanProcessor.create(exporter)).build()).build()
.getTracer("test");
// テストを実行...
validateSpans(exporter.exportedSpans);
}
class TestExporter implements SpanExporter {
public final List<SpanData> exportedSpans = Collections.synchronizedList(new ArrayList<>());
@Override
public CompletableResultCode export(Collection<SpanData> spans) {
exportedSpans.addAll(spans);
return CompletableResultCode.ofSuccess();
}
...
}
[i18n] feedback_question
Thank you. Your feedback is appreciated!
Please let us know how we can improve this page. Your feedback is appreciated!