トレース は、リクエストがアプリケーションに投げられたときに何が起こるかの全体像を教えてくれます。 あなたのアプリケーションが、単一のデータベースを持つモノリスであろうと、洗練されたメッシュサービスであろうと、トレースは、リクエストがアプリケーションの中でたどる完全な「経路」を理解するために不可欠です。
スパンで表現される以下の3つのJSONデータで、これを探ってみましょう。
hello
スパンは次のとおりです。
{
"name": "hello",
"context": {
"trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "0x051581bf3cb55c13"
},
"parent_id": null,
"start_time": "2022-04-29T18:52:58.114201Z",
"end_time": "2022-04-29T18:52:58.114687Z",
"attributes": {
"http.route": "some_route1"
},
"events": [
{
"name": "Guten Tag!",
"timestamp": "2022-04-29T18:52:58.114561Z",
"attributes": {
"event_attributes": 1
}
}
]
}
これはルートスパンであり、オペレーション全体の始まりと終わりを示します。
トレースを示す trace_id
フィールドがありますが、parent_id
がないことに注意してください。
これがルートスパンであることを示します。
hello-greetings
スパンは次のとおりです。
{
"name": "hello-greetings",
"context": {
"trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "0x5fb397be34d26b51"
},
"parent_id": "0x051581bf3cb55c13",
"start_time": "2022-04-29T18:52:58.114304Z",
"end_time": "2022-04-29T22:52:58.114561Z",
"attributes": {
"http.route": "some_route2"
},
"events": [
{
"name": "hey there!",
"timestamp": "2022-04-29T18:52:58.114561Z",
"attributes": {
"event_attributes": 1
}
},
{
"name": "bye now!",
"timestamp": "2022-04-29T18:52:58.114585Z",
"attributes": {
"event_attributes": 1
}
}
]
}
このスパンは、挨拶(greetings
)のような特定のタスクをカプセル化していて、その親は hello
スパンです。
このスパンはルートスパンと同じ trace_id
を共有していて、同じトレースの一部であることを示しています。
さらに、 hello
スパンの span_id
と一致する parent_id
を持っています。
hello-salutations
スパンは次のとおりです。
{
"name": "hello-salutations",
"context": {
"trace_id": "0x5b8aa5a2d2c872e8321cf37308d69df2",
"span_id": "0x93564f51e1abe1c2"
},
"parent_id": "0x051581bf3cb55c13",
"start_time": "2022-04-29T18:52:58.114492Z",
"end_time": "2022-04-29T18:52:58.114631Z",
"attributes": {
"http.route": "some_route3"
},
"events": [
{
"name": "hey there!",
"timestamp": "2022-04-29T18:52:58.114561Z",
"attributes": {
"event_attributes": 1
}
}
]
}
このスパンはこのトレースにおける3つ目の操作を表し、前のスパンと同様にhello
スパンの子です。
また、hello-greetings
スパンの兄弟でもあります。
これらの3つのJSONブロックはすべて同じ trace_id
を共有していて、parent_id
フィールドは階層を表しています。
これは1つのトレースになります!
もうひとつ、各スパンが構造化されたログのように見えることにお気づきでしょう。 それはその通りだからです!トレースについて考える一つの方法は、トレースはコンテキスト、相関関係、階層構造などを持つ構造化されたログの集まりであるということです。 しかし、これらの「構造化されたログ」は、異なるプロセス、サービス、VM、データセンターなどから来る可能性があります。 これにより、トレースはあらゆるシステムのエンドツーエンドのビューを表現できます。
OpenTelemetryでのトレースがどのように機能するかを理解するために、コードの計装の一翼を担う一連のコンポーネントを見てみましょう。
トレーサープロバイダー(TracerProvider
と呼ばれることもあります)は Tracer
のファクトリーです。
ほとんどのアプリケーションでは、トレーサープロバイダーは一度だけ初期化され、そのライフサイクルはアプリケーションのライフサイクルと一致します。
トレーサープロバイダーの初期化には、リソースとエクスポーターの初期化も含まれます。
これは通常、OpenTelemetry によるトレースの最初のステップです。
いくつかの言語SDKでは、グローバルなトレーサープロバイダーがすでに初期化されています。
トレーサーは、サービス内のリクエストなど、与えられた操作で何が起こっているかについての詳細な情報を含むスパンを作成します。 トレーサーはトレーサープロバイダーから作成されます。
トレースエクスポーターはトレースをコンシューマーに送信します。 このコンシューマーは、デバッグや開発時間用の標準出力、OpenTelemetryコレクター、あるいは任意のオープンソースやベンダーのバックエンドです。
コンテキスト伝搬(プロパゲーション)は、分散トレースを可能にする中心となる概念です。 コンテキスト伝搬を使用すると、スパンがどこで生成されたかに関係なく、スパンを相互に関連付け、トレースとして組み立てられます。 このトピックについては、コンテキスト伝搬の概要を参照してください。
スパン は、作業や操作の単位を表します。 スパンはトレースの構成要素です。 OpenTelemetryでは、以下の情報を含みます。
次はスパンの例です。(訳注:JSON形式で表現しているだけで、必ずしもJSONではありません)
{
"name": "/v1/sys/health",
"context": {
"trace_id": "7bba9f33312b3dbb8b2c2c62bb7abe2d",
"span_id": "086e83747d0e381e"
},
"parent_id": "",
"start_time": "2021-10-22 16:04:01.209458162 +0000 UTC",
"end_time": "2021-10-22 16:04:01.209514132 +0000 UTC",
"status_code": "STATUS_CODE_OK",
"status_message": "",
"attributes": {
"net.transport": "IP.TCP",
"net.peer.ip": "172.17.0.1",
"net.peer.port": "51820",
"net.host.ip": "10.177.2.152",
"net.host.port": "26040",
"http.method": "GET",
"http.target": "/v1/sys/health",
"http.server_name": "mortar-gateway",
"http.route": "/v1/sys/health",
"http.user_agent": "Consul Health Check",
"http.scheme": "http",
"http.host": "10.177.2.152:26040",
"http.flavor": "1.1"
},
"events": [
{
"name": "",
"message": "OK",
"timestamp": "2021-10-22 16:04:01.209512872 +0000 UTC"
}
]
}
スパンは、親スパンIDの存在によって暗示されるように、入れ子にできます。 これによって、スパンはアプリケーションで行われる作業をより正確に把握できます。
スパンコンテキストは、各スパンの不変オブジェクトであり、以下を含みます。
スパンコンテキストは、分散コンテキストやバゲッジと共にシリアライズされ、伝搬されるスパンの一部です。
スパンコンテキストにはトレースIDが含まれているため、スパンリンクを作成する際に使用されます。
属性(アトリビュート)はキーと値のペアで、スパンに注釈を付けるためのメタデータを含んでいます。このメタデータは追跡している操作に関する情報を伝えるためのものです。
たとえば、eコマースシステムでユーザーのショッピングカートに商品を追加する操作をスパンが追跡する場合、ユーザーのID、カートに追加する商品のID、カートIDを捕捉できます。
スパンには、スパン作成中または作成後に属性を追加できます。 SDKでのサンプリングで属性を利用できるようにするには、スパン作成時に属性を追加することをおすすめします。 スパン作成後に値を追加する必要がある場合は、その値でスパンを更新してください。
属性には、各言語SDKが実装する以下のルールがあります。
さらに、セマンティック属性があり、これは一般的な操作に通常存在するメタデータのための既知の命名規則です。 システム間で共通の種類のメタデータが標準化されるように、可能な限りセマンティック属性の命名を使用することは有用です。
スパンイベントは、スパン上の構造化ログメッセージ(または注釈)と考えられます。通常、スパンの期間中、意味のある特異な時点を示すために使われます。
たとえば、ウェブブラウザでの2つのシナリオを考えてみましょう。
スパンは、開始と終了がある操作なので、最初のシナリオに最も適しています。
スパンイベントは、意味のある特定の時点を表すため、2つ目のシナリオを追跡するのに最も適しています。
スパンイベントにも属性が含まれるため、属性のかわりにいつイベントを使用するかという質問には、必ずしも明白な答えがあるとは限りません。 決定するための参考として、特定のタイムスタンプに意味があるかどうかを考えてみてください。
たとえば、スパンで操作を追跡していて、操作が完了した時、操作からのデータをテレメトリーに追加したいと思うかもしれません。
リンクは、あるスパンと別の1つ以上のスパンを関連付け、因果関係を示唆するために存在します。 たとえば、ある操作がトレースによって追跡される分散システムがあるとしましょう。
これらの操作のいくつかに対して、追加の操作がキューに入れられ実行されますが、その実行は非同期です。 この後続の操作もトレースで追跡できます。
後続の操作のトレースを最初のトレースに関連付けたいと思っても、後続の操作がいつ始まるかは予測できません。 この2つのトレースを関連付ける必要があるので、スパンリンクを使用します。
最初のトレースの最後のスパンを、2番目のトレースの最初のスパンにリンクできます。 これで、これらのスパンは互いに因果関係があることになります。
リンクは必須ではありませんが、トレーススパン同士を関連付ける良い方法として役立ちます。
各スパンにはステータスがあります。可能な値は以下の3つです。
Unset
Error
Ok
デフォルト値は Unset
です。
スパンのステータスが Unset
である場合は、追跡した操作がエラーなしで正常に完了したということです。
スパンのステータスが Error
である場合、そのスパンが追跡する操作で何らかのエラーが発生したことを意味します。
たとえば、リクエストを処理するサーバーでHTTP 500エラーが発生した場合などです。
スパンのステータスが Ok
である場合、そのスパンはアプリケーションの開発者によって明示的にエラーなしとマークされたことを意味します。
これは直感的ではありませんが、スパンがエラーなく完了したことが分かっている場合、スパンのステータスを Ok
とする必要はありません。
これは Unset
でカバーされるからです。
Ok
は、ユーザーによって明示的に設定されたスパンのステータスの明確な「最終決定」を表すものです。
これは、開発者がスパンの解釈を「成功した」以外のものにはしないことを望む場合に役立ちます。
もう一度確認します。
Unset
はエラーなしで完了したスパンを表します。
Ok
は、開発者が明示的にスパンを成功とマークした場合を表します。
ほとんどの場合、スパンを明示的に Ok
とマークする必要はありません。
スパンが作成されると、Client(クライアント)
、 Server(サーバー)
、 Internal(内部)
、 Producer(プロデューサー)
、 Consumer(コンシューマー)
のいずれかとなります。
このスパンの種類は、トレースがどのように組み立てられるべきかのヒントをトレースバックエンドに提供します。
OpenTelemetryの仕様によると、サーバースパンの親はリモートクライアントスパンであることが多く、クライアントスパンの子は通常サーバースパンです。
同様に、コンシューマースパンの親は、常にプロデューサであり、プロデューサースパンの子は、常にコンシューマである。提供されない場合、スパンの種類は内部的なものとみなされます。
SpanKindの詳細については、SpanKindを参照してください。
クライアントスパンは、発信HTTPリクエストやデータベース呼び出しのような、同期的な発信リモート呼び出しを表します。
この文脈では、「同期」は async/await
を指すのではなく、後の処理のためにキューに入れることが出来ない、ということを指すことに注意してください。
サーバースパンは、HTTPリクエストやリモートプロシージャコールのような、 同期的に着信するリモートコールを表します。
内部スパンは、プロセス境界を越えない操作を表します。 関数呼び出しやNode.jsのExpressミドルウェアの計装などで、内部スパンを使用することがあります。
プロデューサースパンは、後で非同期に処理される可能性のあるジョブの作成を表します。 それは、ジョブキューに挿入されるようなリモートジョブかもしれないし、イベントリスナーによって処理されるローカルジョブかもしれません。
コンシューマースパンは、プロデューサーが作成したジョブの処理を表し、プロデューサースパンがすでに終了したずっと後に開始されることがあります。
詳細はトレース仕様を参照してください。
[i18n] feedback_question
Thank you. Your feedback is appreciated!
Please let us know how we can improve this page. Your feedback is appreciated!