OpenTelemetry Protocol仕様

この記事は英語の原文を日本語に翻訳したものです。原文: https://opentelemetry.io/docs/specs/otlp/

翻訳元: open-telemetry/opentelemetry-proto v1.11.0(コミット 790608c

ステータス:

OpenTelemetry Protocol(OTLP)仕様は、テレメトリー発生源、コレクターなどの中間ノード、テレメトリーバックエンドの間でのテレメトリーデータのエンコーディング・伝送・配送の仕組みを規定します。

OTLPは、OpenTelemetryプロジェクトの範囲内で設計された汎用のテレメトリーデータ配送プロトコルです。

プロトコルの詳細

OTLPは、テレメトリーデータのエンコーディングと、クライアントとサーバーの間でデータを交換するために使われるプロトコルを定義します。

本仕様書は、OTLPがgRPCとHTTPトランスポート上でどのように実装されるかを定義し、ペイロードに使われるProtocol Buffersスキーマを規定します。

OTLPはリクエスト・レスポンス形式のプロトコルです。クライアントがリクエストを送信し、サーバーが対応するレスポンスを返します。本ドキュメントでは、Exportという1つのリクエスト型とレスポンス型を定義します。

すべてのサーバーコンポーネントは、次のトランスポート圧縮オプションをサポートしなければなりません(MUST)。

  • 圧縮なし。noneと表記します。
  • Gzip圧縮。gzipと表記します。

OTLP/gRPC

下層のgRPCトランスポートを確立した後、クライアントはExport*ServiceRequestメッセージ(ログ用のExportLogsServiceRequest、メトリクス用のExportMetricsServiceRequest、トレース用のExportTraceServiceRequest、プロファイル用のExportProfilesServiceRequest)を使ったユナリーリクエストでテレメトリーデータの送信を開始します。クライアントはサーバーへリクエストの列を継続的に送信し、それぞれのリクエストに対するレスポンスを受け取ることを期待します。

リクエストとレスポンス

注: このプロトコルは、クライアントとサーバーの1つのペアの間の配送信頼性に関するものであり、クライアントとサーバーの間の伝送中にデータが失われないことを保証することを目的としています。多くのテレメトリー収集システムには、最終的な宛先に到達するまでにデータが経由する中間ノードがあります(例: アプリケーション → エージェント → コレクター → バックエンド)。このようなシステムにおけるエンドツーエンドの配送保証は、OTLPの範囲外です。本プロトコルで説明する確認応答は、単一のクライアント・サーバーのペアの間で行われるものであり、複数ホップの配送経路にある中間ノードにまで及びません。

OTLP/gRPCリクエスト

サーバーは、設定ミスや悪意のあるクライアントが過大なリクエストを送信することによる過剰なメモリ使用を軽減するため、リクエストを受信する際に(展開後も含めて)メッセージサイズの上限を強制しなければなりません(MUST)。サーバー実装は通常、受信メッセージサイズの既定の上限として4 MiBを強制します。しかし、既定の上限として64 MiBを使うことが推奨されます(RECOMMENDED)。実装はこの上限を設定可能にすべきです(SHOULD)。上限を超えた場合、gRPCサーバー実装は非リトライ可能なエラーとしてRESOURCE_EXHAUSTEDコードを報告しなければなりません(MUST)。

クライアントは、サーバーへの過負荷を避けるため、(圧縮前も含めて)リクエストメッセージのサイズを制限すべきです(SHOULD)。既定の上限として64 MiBを使うことが推奨されます(RECOMMENDED)。実装はこの上限を設定可能にすべきです(SHOULD)。上限を超えた場合、クライアントはそのリクエストを行ってはならず(MUST NOT)、リクエストが破棄された事実を記録すべきです(SHOULD)。

OTLP/gRPC同時リクエスト

リクエストを送信した後、クライアントはサーバーからレスポンスを受け取るまで待機してもよい(MAY)です。その場合、サーバーによって未確認のリクエストは最大でも1つしか存在しません。

ユナリー

実装の単純さが望まれる場合や、クライアントとサーバーが非常に低レイテンシーなネットワーク(例えばクライアントが計装対象のアプリケーションで、サーバーがローカルデーモン(エージェント)として動作するOpenTelemetry Collectorである場合)で接続されている場合には、逐次実行が推奨されます。

高いスループットを達成する必要がある実装は、より高いスループットを達成するために同時ユナリー呼び出しをサポートすべきです(SHOULD)。クライアントは、先に送信したリクエストへのレスポンスを待たずに新しいリクエストを送信すべきで(SHOULD)、これにより現在未確認のまま飛行中のリクエストのパイプラインが実質的に作られます。

同時実行

同時リクエストの数は設定可能であるべきです(SHOULD)。

達成可能な最大スループットはmax_concurrent_requests * max_request_size / (network_latency + server_response_time)です。例えば、1つのリクエストに最大100個のスパンを含められ、ネットワークの往復レイテンシーが200ms、サーバーの応答時間が300msであれば、同時リクエストが1つの場合の達成可能な最大スループットは100 spans / (200ms+300ms)、つまり毎秒200スパンになります。高レイテンシーなネットワークや、サーバーの応答時間が長い場合に良好なスループットを達成するには、リクエストを非常に大きくするか、多数の同時リクエストを行う必要があることがわかります。

クライアントがシャットダウンする場合(例えば、それを含むプロセスが終了しようとする場合)、クライアントはすべての保留中の確認応答を受け取るまで、または実装固有のタイムアウトが経過するまで、任意に待機します。これによりテレメトリーデータの確実な配送が保証されます。クライアント実装は、シャットダウン中の待機をオンまたはオフにするオプションを提供すべきです(SHOULD)。

クライアントが特定のリクエストを配送できない場合(例えば確認応答を待っている間にタイマーが失効した場合)、クライアントはデータが配送されなかった事実を記録すべきです(SHOULD)。

OTLP/gRPCレスポンス

レスポンスは適切なメッセージでなければなりません(MUST)(完全な成功部分的な成功失敗の各ケースで使うべき具体的なメッセージについては以下を参照してください)。

クライアントは、設定ミスや悪意のあるサーバーによる過剰なメモリ使用を軽減するため、レスポンスを受信する際に(展開後も含めて)メッセージサイズの上限を強制しなければなりません(MUST)。gRPCクライアント実装は通常、受信メッセージサイズの既定の上限として4 MiBを強制しますが、これは使用しても構いません。実装はこの上限を設定可能にすべきです(SHOULD)。上限を超えた場合、クライアントはそのレスポンスを非リトライ可能なエラーとして扱わなければなりません(MUST)。なお、このようなシナリオでは、gRPCクライアント実装は呼び出し元にRESOURCE_EXHAUSTEDコードを報告します。

サーバーは、クライアントへの過負荷を避けるため、(圧縮前も含めて)レスポンスメッセージのサイズを制限しなければなりません(MUST)。既定の上限として4 MiBを使うことが推奨されます(RECOMMENDED)。実装はこの上限を設定可能にすべきです(SHOULD)。上限を超えることになる部分的な成功のレスポンスについては、可能かつ実用的であれば、レスポンスの意味を変えずにレスポンスサイズを削減すべきです(SHOULD)。そのために、サーバーはpartial_success.error_messageのような任意の診断フィールドの詳細度を下げたり、任意の診断フィールドを省略したりしてもよい(MAY)です。それでもレスポンスが上限に収まらない場合、サーバーは非リトライ可能なエラーとしてRESOURCE_EXHAUSTEDコードでそのリクエストを失敗させなければなりません(MUST)。このような失敗の後、リクエストに含まれていたテレメトリーデータが受理されたかどうかは規定されません。

完全な成功

成功レスポンスは、テレメトリーデータがサーバーに正常に受理されたことを示します。

サーバーが空のリクエスト(テレメトリーデータを一切含まないリクエスト)を受信した場合、サーバーは成功で応答すべきです(SHOULD)。

成功時、サーバーのレスポンスはExport<signal>ServiceResponseメッセージ(トレース用のExportTraceServiceResponse、メトリクス用のExportMetricsServiceResponse、ログ用のExportLogsServiceResponse、プロファイル用のExportProfilesServiceResponse)でなければなりません(MUST)。

サーバーは、成功レスポンスの場合、partial_successフィールドを未設定のままにしなければなりません(MUST)。

部分的な成功

リクエストが部分的にのみ受理された場合(つまりサーバーがデータの一部のみを受理し、残りを拒否した場合)、サーバーのレスポンスは完全な成功のケースと同じExport<signal>ServiceResponseメッセージでなければなりません(MUST)。

さらに、サーバーはpartial_successフィールド(トレース用のExportTracePartialSuccessメッセージ、メトリクス用のExportMetricsPartialSuccessメッセージ、ログ用のExportLogsPartialSuccessメッセージ、プロファイル用のExportProfilesPartialSuccess)を初期化しなければならず(MUST)、拒否したスパン・データポイント・ログレコード・プロファイルの数を、それぞれ対応するrejected_spansrejected_data_pointsrejected_log_recordsrejected_profilesフィールドに設定しなければなりません(MUST)。

サーバーは、error_messageフィールドに英語での人間が読めるエラーメッセージを設定すべきです(SHOULD)。このメッセージでは、サーバーがデータの一部を拒否した理由を説明し、問題への対処方法についてのガイダンスを提供してもよいでしょう。本プロトコルはこのエラーメッセージの構造を定義しません。

サーバーは、リクエストを完全に受理した場合であっても、partial_successフィールドを使ってクライアントへ警告や提案を伝えてもよい(MAY)です。この場合、rejected_<signal>フィールドの値は0でなければならず(MUST)、error_messageフィールドは空でないものでなければなりません(MUST)。

クライアントは、partial_successが設定された部分的な成功のレスポンスを受け取った場合、そのリクエストをリトライしてはなりません(MUST NOT)。

失敗

サーバーがエラーを返す場合、それは次の2つの大きなカテゴリーのいずれかに分類されます。リトライ可能なエラーと、リトライ不可能なエラーです。

  • リトライ可能なエラーは、テレメトリーデータの処理が失敗したことを示し、クライアントはそのエラーを記録し、同じデータの再エクスポートを試みてもよいでしょう(SHOULD)。例えば、サーバーが一時的にデータを処理できない場合にこれが発生します。

  • リトライ不可能なエラーは、テレメトリーデータの処理が失敗したことを示し、クライアントは同じテレメトリーデータの再送信を試みてはなりません(MUST NOT)。クライアントはそのテレメトリーデータを破棄しなければなりません(MUST)。例えば、リクエストに不正なデータが含まれていて、サーバーがそれを逆シリアル化または処理できない場合にこれが発生します。クライアントは、このように破棄したデータのカウンターを保持すべきです(SHOULD)。

サーバーは、リトライ可能なエラーを示すためにUnavailableコードを使うべきで(SHOULD)、RetryInfoを使ってstatusを介した追加の詳細を提供してもよい(MAY)です。以下はこれを示すGoのサンプルコードです。

  // Do this on server side.
  st, err := status.New(codes.Unavailable, "Server is unavailable").
    WithDetails(&errdetails.RetryInfo{RetryDelay: &duration.Duration{Seconds: 5}})
  if err != nil {
    log.Fatal(err)
  }

  return st.Err()

リトライ不可能なエラーを示すには、サーバーはInvalidArgumentコードを使うことが推奨されており、BadRequestを使ってstatusを介した追加の詳細を提供してもよい(MAY)です。より適切であれば、別のgRPCステータスコードを使ってもよいでしょう。以下はこれを示すGoのサンプルコードの一部です。

  // Do this on the server side.
  st, err := status.New(codes.InvalidArgument, "Invalid Argument").
    WithDetails(&errdetails.BadRequest{})
  if err != nil {
    log.Fatal(err)
  }

  return st.Err()

サーバーは、特定のエラー状況に対してより適切な場合、リトライ可能・リトライ不可能を示すために他のgRPCコードを使ってもよい(MAY)です。クライアントは、次の表に従ってgRPCステータスコードをリトライ可能・リトライ不可能として解釈すべきです(SHOULD)。

gRPC CodeRetryable?
CANCELLEDYes
UNKNOWNNo
INVALID_ARGUMENTNo
DEADLINE_EXCEEDEDYes
NOT_FOUNDNo
ALREADY_EXISTSNo
PERMISSION_DENIEDNo
UNAUTHENTICATEDNo
RESOURCE_EXHAUSTEDOnly if the server can recover (see below)
FAILED_PRECONDITIONNo
ABORTEDYes
OUT_OF_RANGEYes
UNIMPLEMENTEDNo
INTERNALNo
UNAVAILABLEYes
DATA_LOSSYes

リトライする際、クライアントは指数バックオフ戦略を実装すべきです(SHOULD)。この例外は、後述するスロットリングのケースで、これはリトライ間隔について明示的な指示を提供します。

クライアントは、サーバーがリソース枯渇からの回復が可能であることを示している場合に限り、RESOURCE_EXHAUSTEDコードをリトライ可能として解釈すべきです(SHOULD)。これは、サーバーがRetryInfoを含むstatusを返すことで示されます。この場合、サーバーとクライアントの挙動はOTLP/gRPCスロットリング節で説明したとおりになります。そのようなステータスが返されない場合、RESOURCE_EXHAUSTEDコードはリトライ不可能として扱われるべきです(SHOULD)。

OTLP/gRPCスロットリング

OTLPはバックプレッシャーの通知を許可します。

サーバーがクライアントから受信するデータのペースに追いつけない場合、サーバーはその事実をクライアントに通知すべきです(SHOULD)。クライアントはサーバーへの過負荷を避けるため、自身をスロットリングしなければなりません(MUST)。

gRPCトランスポートを使う場合にバックプレッシャーを通知するには、サーバーはUnavailableコードのエラーを返すべきで(SHOULD)、RetryInfoを使ってstatusを介した追加の詳細を提供してもよい(MAY)です。以下はこれを示すGoのサンプルコードの一部です。

  // Do this on the server side.
  st, err := status.New(codes.Unavailable, "Server is unavailable").
    WithDetails(&errdetails.RetryInfo{RetryDelay: &duration.Duration{Seconds: 30}})
  if err != nil {
    log.Fatal(err)
  }

  return st.Err()

  ...

  // Do this on the client side.
  st := status.Convert(err)
  for _, detail := range st.Details() {
    switch t := detail.(type) {
    case *errdetails.RetryInfo:
      if t.RetryDelay.Seconds > 0 || t.RetryDelay.Nanos > 0 {
        // Wait before retrying.
      }
    }
  }

クライアントがこの通知を受け取った場合、RetryInfoのドキュメントに記載された推奨事項に従うべきです(SHOULD)。

// Describes when the clients can retry a failed request. Clients could ignore
// the recommendation here or retry when this information is missing from the error
// responses.
//
// It's always recommended that clients should use exponential backoff when
// retrying.
//
// Clients should wait until `retry_delay` amount of time has passed since
// receiving the error response before retrying.  If retrying requests also
// fail, clients should use an exponential backoff scheme to increase gradually
// the delay between retries based on `retry_delay` until either a maximum
// number of retries has been reached, or a maximum retry delay cap has been
// reached.

retry_delayの値はサーバーによって決定され、実装依存です。サーバーは、サーバーが回復するための十分な時間を確保できるほど大きく、かつスロットリングされている間にクライアントがデータを破棄してしまうほど大きくないretry_delay値を選ぶべきです(SHOULD)。

OTLP/gRPCサービスとProtobuf定義

gRPCサービス定義はこちらにあります。

リクエストとレスポンスのProtobuf定義はこちらにあります。

protoのバージョンと成熟度レベルを必ず確認してください。シグナルによってスキーマの成熟度レベルが異なる場合があります。安定しているものもあれば、ベータ版のものもあります。

OTLP/gRPCの既定のポート

OTLP/gRPCの既定のネットワークポートは4317です。

OTLP/HTTP

OTLP/HTTPは、バイナリ形式またはJSON形式でエンコードされたProtobufペイロードを使います。エンコーディングに関わらず、メッセージのProtobufスキーマはOTLP/HTTPとOTLP/gRPCで同じであり、こちらで定義されています。

OTLP/HTTPは、クライアントからサーバーへテレメトリーデータを送信するためにHTTP POSTリクエストを使います。実装はHTTP/1.1またはHTTP/2トランスポートを使ってもよい(MAY)です。HTTP/2トランスポートを使う実装は、HTTP/2接続を確立できない場合にHTTP/1.1トランスポートへフォールバックすべきです(SHOULD)。

バイナリProtobufエンコーディング

バイナリProtobufでエンコードされたペイロードは、proto3のエンコーディング標準を使います。

クライアントとサーバーは、バイナリProtobufでエンコードされたペイロードを送信する際、リクエストとレスポンスのヘッダーに「Content-Type: application/x-protobuf」を設定しなければなりません(MUST)。

JSON Protobufエンコーディング

JSON Protobufでエンコードされたペイロードは、ProtobufとJSONの間のマッピングについてproto3標準で定義されたJSONマッピングを使いますが、そのマッピングから次の点で異なります。

  • traceIdspanIdのバイト配列は、大文字小文字を区別しない16進エンコード文字列として表現されます。標準のProtobuf JSONマッピングで定義されているようなbase64エンコードは使いません。16進エンコーディングは、SpanLinkLogRecordなどのすべてのOTLP ProtobufメッセージにおけるtraceIdspanIdフィールドで使われます。例えば、SpanのtraceIdフィールドは次のように表現できます。 { “traceId”: “5B8EFFF798038103D269B633813FC60C”, … }

  • enumフィールドの値は整数値としてエンコードされなければなりません(MUST)。標準のProtobuf JSONマッピングがenumフィールドの値を整数値またはenum名文字列のいずれかでエンコードできることを許しているのとは異なり、OTLP JSON Protobufエンコーディングでは整数のenum値のみが許可され、enum名文字列は使ってはなりません(MUST NOT)。例えば、SpanでSPAN_KIND_SERVERという値を持つkindフィールドは次のように表現できます。{ “kind”: 2, … }

  • OTLP/JSONレシーバーは、未知の名前を持つメッセージフィールドを無視しなければならず(MUST)、その未知のフィールドがペイロードに存在しなかった場合と同様にメッセージをアンマーシャルしなければなりません(MUST)。これはバイナリProtobufアンマーシャラーの挙動と整合しており、OTLPメッセージに新しいフィールドを追加しても既存のレシーバーが壊れないことを保証します。

  • JSONオブジェクトのキーは、lowerCamelCaseに変換されたフィールド名です。元のフィールド名をJSONオブジェクトのキーとして使うことは有効ではありません。例えば、Resourceの有効なJSON表現は次のとおりです。 { "attributes": {...}, "droppedAttributesCount": 123 } 次は有効な表現ではありません。 { "attributes": {...}, "dropped_attributes_count": 123 }

なお、Protobuf仕様によれば、JSONエンコードされたペイロードにおける64ビット整数の数値は10進数の文字列としてエンコードされ、デコード時には数値または文字列のいずれも受け付けられます。

クライアントとサーバーは、JSON Protobufでエンコードされたペイロードを送信する際、リクエストとレスポンスのヘッダーに「Content-Type: application/json」を設定しなければなりません(MUST)。

JSONペイロードの例については、OTLP JSONリクエストの例を参照してください。

OTLP/HTTPリクエスト

テレメトリーデータはHTTP POSTリクエストで送信されます。POSTリクエストの本文は、バイナリエンコードされたProtobuf形式、またはJSONエンコードされたProtobuf形式のいずれかのペイロードです。

トレースデータを運ぶリクエストの既定のURLパスは/v1/tracesです(例えば「example.com」サーバーに接続する場合の完全なURLはhttps://example.com/v1/tracesになります)。リクエスト本文は、ProtobufでエンコードされたExportTraceServiceRequestメッセージです。

メトリクスデータを運ぶリクエストの既定のURLパスは/v1/metricsで、リクエスト本文はProtobufでエンコードされたExportMetricsServiceRequestメッセージです。

ログデータを運ぶリクエストの既定のURLパスは/v1/logsで、リクエスト本文はProtobufでエンコードされたExportLogsServiceRequestメッセージです。

プロファイリングデータを運ぶリクエストの既定のURLパスは/v1development/profilesで、リクエスト本文はProtobufでエンコードされたExportProfilesServiceRequestメッセージです。

クライアントはコンテンツをgzip圧縮してもよい(MAY)です。その場合、「Content-Encoding: gzip」リクエストヘッダーを含めなければなりません(MUST)。クライアントは、gzipエンコードされたレスポンスを受信できる場合、「Accept-Encoding: gzip」リクエストヘッダーを含めてもよい(MAY)です。

リクエストの既定でないURLパスは、クライアントとサーバーの両側で設定できます(MAY)。

サーバーは、設定ミスや悪意のあるクライアントが過大なリクエストを送信することによる過剰なメモリ使用を軽減するため、リクエスト本文を解析する際に(展開後も含めて)サイズの上限を強制しなければなりません(MUST)。既定の上限として64 MiBを使うことが推奨されます(RECOMMENDED)。実装はこの上限を設定可能にすべきです(SHOULD)。上限を超えた場合、サーバーはHTTP 413 Content Too Largeで応答しなければなりません(MUST)。

クライアントは、サーバーへの過負荷を避けるため、(圧縮前も含めて)リクエスト本文のサイズを制限すべきです(SHOULD)。既定の上限として64 MiBを使うことが推奨されます(RECOMMENDED)。実装はこの上限を設定可能にすべきです(SHOULD)。上限を超えた場合、クライアントはそのリクエストを行ってはならず(MUST NOT)、リクエストが破棄された事実を記録すべきです(SHOULD)。

OTLP/HTTPレスポンス

レスポンス本文は適切なシリアライズ済みProtobufメッセージでなければなりません(MUST)(完全な成功部分的な成功失敗の各ケースで使うべき具体的なメッセージについては以下を参照してください)。

クライアントは、設定ミスや悪意のあるサーバーによる過剰なメモリ使用を軽減するため、レスポンス本文を解析する際に(展開後も含めて)サイズの上限を強制しなければなりません(MUST)。既定の上限として4 MiBを使うことが推奨されます(RECOMMENDED)。実装はこの上限を設定可能にすべきです(SHOULD)。上限を超えた場合、クライアントはそのレスポンスを非リトライ可能なエラーとして扱わなければならず(MUST)、レスポンスが破棄された事実を記録すべきです(SHOULD)。

サーバーは、クライアントへの過負荷を避けるため、(圧縮前も含めて)レスポンス本文のサイズを制限しなければなりません(MUST)。既定の上限として4 MiBを使うことが推奨されます(RECOMMENDED)。実装はこの上限を設定可能にすべきです(SHOULD)。上限を超えることになる部分的な成功のレスポンスについては、可能かつ実用的であれば、レスポンスの意味を変えずにレスポンスサイズを削減すべきです(SHOULD)。そのために、サーバーはpartial_success.error_messageのような任意の診断フィールドの詳細度を下げたり、任意の診断フィールドを省略したりしてもよい(MAY)です。それでもレスポンスが上限に収まらない場合、サーバーはHTTP 500 Internal Server Errorでそのリクエストを失敗させなければなりません(MUST)。このような失敗の後、リクエストに含まれていたテレメトリーデータが受理されたかどうかは規定されません。

サーバーは、レスポンス本文がバイナリエンコードされたProtobufペイロードである場合、「Content-Type: application/x-protobuf」ヘッダーを設定しなければなりません(MUST)。サーバーは、レスポンスがJSONエンコードされたProtobufペイロードである場合、「Content-Type: application/json」を設定しなければなりません(MUST)。サーバーは、リクエストで受信した「Content-Type」と同じものをレスポンスで使わなければなりません(MUST)。

リクエストヘッダーに「Accept-Encoding: gzip」が存在する場合、サーバーはレスポンスをgzipエンコードし、「Content-Encoding: gzip」レスポンスヘッダーを設定してもよい(MAY)です。

完全な成功

成功レスポンスは、テレメトリーデータがサーバーに正常に受理されたことを示します。

サーバーが空のリクエスト(テレメトリーデータを一切含まないリクエスト)を受信した場合、サーバーは成功で応答すべきです(SHOULD)。

成功時、サーバーはHTTP 200 OKで応答しなければなりません(MUST)。レスポンス本文は、ProtobufでエンコードされたExport<signal>ServiceResponseメッセージ(トレース用のExportTraceServiceResponse、メトリクス用のExportMetricsServiceResponse、ログ用のExportLogsServiceResponse、プロファイル用のExportProfilesServiceResponse)でなければなりません(MUST)。

サーバーは、成功レスポンスの場合、partial_successフィールドを未設定のままにしなければなりません(MUST)。

部分的な成功

リクエストが部分的にのみ受理された場合(つまりサーバーがデータの一部のみを受理し、残りを拒否した場合)、サーバーはHTTP 200 OKで応答しなければなりません(MUST)。レスポンス本文は、完全な成功のケースと同じExport<signal>ServiceResponseメッセージでなければなりません(MUST)。

さらに、サーバーはpartial_successフィールド(トレース用のExportTracePartialSuccessメッセージ、メトリクス用のExportMetricsPartialSuccessメッセージ、ログ用のExportLogsPartialSuccessメッセージ、プロファイル用のExportProfilesPartialSuccess)を初期化しなければならず(MUST)、拒否したスパン・データポイント・ログレコードの数を、対応するrejected_spansrejected_data_pointsrejected_log_recordsまたはrejected_profilesフィールドに設定しなければなりません(MUST)。

サーバーは、error_messageフィールドに英語での人間が読めるエラーメッセージを設定すべきです(SHOULD)。このメッセージでは、サーバーがデータの一部を拒否した理由を説明し、問題への対処方法についてのガイダンスを提供してもよいでしょう。本プロトコルはこのエラーメッセージの構造を定義しません。

サーバーは、リクエストを完全に受理した場合であっても、partial_successフィールドを使ってクライアントへ警告や提案を伝えてもよい(MAY)です。この場合、rejected_<signal>フィールドの値は0でなければならず(MUST)、error_messageフィールドは空でないものでなければなりません(MUST)。

クライアントは、partial_successが設定された部分的な成功のレスポンスを受け取った場合、そのリクエストをリトライしてはなりません(MUST NOT)。

失敗

リクエストの処理が失敗した場合、サーバーは適切なHTTP 4xxまたはHTTP 5xxステータスコードで応答しなければなりません(MUST)。具体的な失敗ケースと使うべきHTTPステータスコードの詳細については、以下の節を参照してください。

すべてのHTTP 4xxHTTP 5xxレスポンスの本文は、問題を説明する、ProtobufでエンコードされたStatusメッセージでなければなりません(MUST)。

本仕様書はStatus.codeフィールドを使わず、サーバーはStatus.codeフィールドを省略してもよい(MAY)です。クライアントはStatus.codeフィールドに基づいて挙動を変えることを期待されませんが、トラブルシューティングのためにこれを記録してもよい(MAY)です。

Status.messageフィールドには、Statusメッセージスキーマで定義された開発者向けのエラーメッセージを含めるべきです(SHOULD)。

サーバーは、追加の詳細を含むStatus.detailsフィールドを含めてもよい(MAY)です。このフィールドが各失敗ケースで何を含められるかについては以下を参照してください。

サーバーは、特定のエラー状況に対してリトライ可能・リトライ不可能を示すためにHTTPレスポンスステータスコードを使うべきです(SHOULD)。クライアントは、HTTPレスポンスステータスコードをリトライ可能・リトライ不可能として尊重すべきです(SHOULD)。

リトライ可能なレスポンスコード

次の表に列挙されたレスポンスステータスコードを受け取ったリクエストはリトライされるべきです(SHOULD)。それ以外の4xxまたは5xxレスポンスステータスコードはリトライしてはなりません(MUST NOT)。

HTTP response status code
429 Too Many Requests
502 Bad Gateway
503 Service Unavailable
504 Gateway Timeout
不正なデータ

リクエストの処理が、デコードできない、または他の理由で無効なデータを含んでいたために失敗し、その失敗が永続的なものである場合、サーバーはHTTP 400 Bad Requestで応答しなければなりません(MUST)。レスポンスのStatus.detailsフィールドには、不正なデータを説明するBadRequestを含めるべきです(SHOULD)。

クライアントは、HTTP 400 Bad Requestレスポンスを受け取った場合、そのリクエストをリトライしてはなりません(MUST NOT)。

OTLP/HTTPスロットリング

サーバーが、クライアントに許可された数を超えるリクエストを受信した場合、またはサーバーが過負荷になっている場合、サーバーはHTTP 429 Too Many RequestsまたはHTTP 503 Service Unavailableで応答すべきで(SHOULD)、クライアントがリトライまでどれだけ待つべきかを示す“Retry-After”ヘッダーを含めてもよい(MAY)です。この値はHTTP日付形式、またはレスポンスを受信してから遅延させる秒数のいずれかであることに注意してください。

クライアントは、“Retry-After"ヘッダーが存在する場合、その値を尊重すべきです(SHOULD)。クライアントがリトライ可能なエラーコード(上の表を参照)を受け取り、レスポンスに"Retry-After"ヘッダーが存在しない場合、クライアントはリトライの間に指数バックオフ戦略を実装すべきです(SHOULD)。

その他すべてのレスポンス

本ドキュメントで明示的に列挙されていないその他すべてのHTTPレスポンスは、HTTP仕様に従って扱われるべきです。

サーバーがレスポンスを返さずに切断した場合、クライアントはリトライして同じリクエストを送信すべきです(SHOULD)。クライアントは、サーバーへの過負荷を避けるため、リトライの間に指数バックオフ戦略を実装すべきです(SHOULD)。

OTLP/HTTP接続

クライアントがサーバーに接続できない場合、クライアントはリトライの間に指数バックオフ戦略を使って接続をリトライすべきです(SHOULD)。リトライ間の間隔にはランダムなジッターを持たせなければなりません。

クライアントはリクエストの間、接続を維持すべきです(SHOULD)。

サーバー実装は、バイナリエンコードされたProtobufペイロードのOTLP/HTTPリクエストと、JSONエンコードされたProtobufペイロードのOTLP/HTTPリクエストを同じポートで受け付け、「Content-Type」リクエストヘッダーに基づいて対応するペイロードデコーダーへ多重化すべきです(SHOULD)。

サーバー実装は、OTLP/gRPCとOTLP/HTTPのリクエストを同じポートで受け付け、「Content-Type」リクエストヘッダーに基づいて対応するトランスポートハンドラーへ接続を多重化してもよい(MAY)です。

OTLP/HTTP同時リクエスト

より高い総スループットを達成するため、クライアントは複数の並列HTTP接続を使ってリクエストを送信してもよい(MAY)です。その場合、並列接続の最大数は設定可能であるべきです(SHOULD)。

OTLP/HTTPの既定のポート

OTLP/HTTPの既定のネットワークポートは4318です。

実装に関する推奨事項

マルチデスティネーションエクスポート

1つのクライアントが複数の宛先サーバーへテレメトリーデータを送信しなければならない場合、追加の複雑さを考慮する必要があります。一方のサーバーがデータを確認応答し、もう一方のサーバーがまだ確認応答していない場合、クライアントはどのように先へ進めるかを決める必要があります。

このような状況では、クライアントは宛先ごとにキューイング・確認応答処理・リトライのロジックを実装すべきです(SHOULD)。これにより、サーバー同士が互いをブロックしないことが保証されます。キューは、複数のキューを持つことによるメモリオーバーヘッドを最小化するため、共有された不変のデータを参照すべきです(SHOULD)。

マルチデスティネーションエクスポート

これにより、すべての宛先サーバーが、それぞれの受信速度にかかわらず(クライアント側のキューサイズによって課される利用可能な上限の範囲内で)データを受信できることが保証されます。

空のテレメトリーエンベロープ

特定の状況下では、内容が空のテレメトリーエンベロープが発生することがあります。例としては、ScopeMetricsを含まないResourceMetrics、Metricsを含まないResourceMetrics、あるいはLogsやSpansにおける同様のケースが考えられます。これが発生する1つの経路としては、フィルタリングルールがすべての含まれるデータポイントを取り除く場合が考えられますが、他にも原因はあります。

実際には、こうした空のエンベロープは既存の実装によってしばしば破棄されます。それを踏まえ、送信者は空のエンベロープ(スパン数・メトリクスポイント数・ログレコード数がゼロのOTLPペイロード)を作成すべきではなく(SHOULD NOT)、受信者は空のエンベロープを無視してもよく(MAY)、OTLPペイロードを受信して送信(転送)する実装は空のエンベロープを破棄してもよい(MAY)です。

既知の制限

リクエストの確認応答

重複データ

エッジケース(再接続時、ネットワーク中断時など)では、確認応答をまだ受け取っていない場合、クライアントは最近送信したデータが配送されたかどうかを知る方法がありません。クライアントは通常、配送を保証するためにそうしたデータを再送信することを選び、その結果サーバー側で重複データが生じる可能性があります。これは意図的な選択であり、テレメトリーデータにとって正しいトレードオフだと考えられています。

将来のバージョンと相互運用性

OTLPは時間の経過とともに進化し変化していきます。OTLPの将来のバージョンは、異なるバージョンのOTLPを実装するクライアントとサーバーが相互運用してテレメトリーデータを交換できることを保証するように設計・実装されなければなりません。古いクライアントは新しいサーバーと通信できなければならず、その逆もまた同様です。OTLPの新しいバージョンが、OTLPの古いバージョンを実装しているノードには理解・サポートできない新機能を導入する場合を考えます。その場合、プロトコルは機能的な観点から最小公倍数へ後退しなければなりません。

可能な限り、廃止が宣言されていないOTLPのすべてのバージョンの間で相互運用性が保証されなければなりません(MUST)。

OTLPは明示的なプロトコルバージョン番号を使いません。異なるバージョンのクライアントとサーバーのOTLPの相互運用性は、次の考え方に基づいています。

  1. OTLP(現在および将来のバージョン)は一連のケーパビリティを定義し、その一部は必須で、他は任意です。クライアントとサーバーは必須のケーパビリティを実装しなければならず、任意のケーパビリティについてはその一部だけを実装することを選んでも構いません。

  2. プロトコルへの小さな変更については、OTLPの将来のバージョンと拡張は、後方互換性のある方法でメッセージスキーマを進化させるProtobufの能力を使うことが推奨されます。OTLPの新しいバージョンは、それらのフィールドを理解しないクライアントやサーバーには無視されるような新しいフィールドをメッセージに追加できます。多くの場合、そうしたスキーマ変更を注意深く設計し、新しいフィールドの既定値を正しく選ぶことで、ピアノードが異なるケーパビリティを持つことを明示的に検出しなくても、異なるバージョン間の相互運用性を確保できます。

  3. より重大な変更は、将来のOTEPにおいて新しい任意のケーパビリティとして明示的に定義されなければなりません。そうしたケーパビリティは、下層のトランスポートを確立した後にクライアントとサーバーの実装によって発見されるべきです(SHOULD)。具体的な発見の仕組みは将来のOTEPで記述されるべきで(SHOULD)、通常はクライアントからサーバーへの発見リクエスト・レスポンスのメッセージ交換を実装することで実現できます。新しいケーパビリティを定義するOTEPが記述します。本仕様書で定義された必須のケーパビリティは暗黙的であり、発見を必要としません。新しい任意のケーパビリティをサポートする実装は、その特定のケーパビリティをサポートしないピアの期待に合わせて挙動を調整しなければなりません(MUST)。

用語集

テレメトリーデータの交換には2つの当事者が関与します。本ドキュメントでは、テレメトリーデータの発生源となる当事者をClient、テレメトリーデータの宛先となる当事者をServerと呼びます。

クライアント・サーバー

Clientの例は、計装対象のアプリケーションやテレメトリーコレクターの送信側であり、Serverの例は、テレメトリーバックエンドやテレメトリーコレクターの受信側です(つまりコレクターは、どちら側から見るかによって通常ClientとServerの両方になります)。

ClientとServerはどちらもNodeでもあります。本ドキュメントでは、この用語をどちらか一方を指す場合に使います。

参考文献

  • OTEP 0035 OpenTelemetry Protocol Specification
  • OTEP 0099 OTLP/HTTP: HTTP Transport Extension for OTLP
  • OTEP 0122 OTLP: JSON Encoding for OTLP/HTTP