OTEP-0202: イベントとログのAPIの導入
OpenTelemetryのログシグナルに基づき、LogRecord データモデルと LogEmitter SDKに支えられた、イベントとログのAPIを導入します。
動機
OpenTelemetryの観点では、ログレコードとイベントは同じコンセプトの異なる名前です。 しかし、それらが基盤となるデータモデルを使ってどのように表現されるかには、以下で説明する微妙な違いがあります。 既存のロギングAPIがなぜイベントを作成する目的には十分でないのかを説明します。 そうすれば、イベントを作成するためのAPIがOpenTelemetryに必要であることが明らかになるでしょう。 ここでいうイベントは、独立したイベントを指しており、スパンのコンテキストでのみ発生するスパンイベントとは区別されることに注意してください。
ここで導入するAPIのログ部分は、ログアペンダーのみが使用することを想定しており、エンドユーザーは引き続き各言語で利用可能なロギングAPIを使用すべきです。
ログとイベントの微妙な違い
ログには、イベントにはない、第一級のパラメーターとして必須の重大度レベルがあります。 一方、イベントにはログにはない必須の名前があります。 さらに、ログは通常、文字列形式のメッセージを持ち、イベントはキーバリューペアの形式でデータを持ちます。 このため、両者のAPIインターフェースの要件はわずかに異なります。
イベントAPIを必要とするのは誰か
イベントの記録を必要とする状況をいくつか挙げますが、他にもあるでしょう。 トレースAPIはイベントを記録する機能を提供しますが、それはスパンが進行中の場合に限られることに注意してください。 独立したイベントを記録するためには、別のAPIが必要です。
- RUMイベント(クライアントサイドの計装)
- エラー、ユーザーの操作イベント、ウェブバイタルなど、進行中のスパンがない状況で発生する独立したイベント。
- Kubernetesイベントの記録
- Collectorのエンティティイベント リンク
- データモデルの例のマッピングで説明されている、その他いくつかのイベントシステム。
現在のログAPIインターフェースはイベントに使えるか
- ログレベルは、ほぼすべての言語のロギングAPIにおいて基本的な要素です。
Logインターフェースのすべてのメソッドはログレベルにちなんで名付けられており、通常、ログレベルなしでログエントリーを送信する汎用のメソッドはありません。
- Web向けのJavaScriptでは、標準的なロギングの方法は
console.logを使うことです。 イベントは Event/CustomEvent インターフェースを使って作成できます。 しかし、これらのログとイベントに対してカスタムの送信先を定義するオプションはありません。 ログはコンソールにのみ送られ、イベントリスナーはそれをディスパッチするDOM要素にアタッチされます。 - Androidでは、
android.util.LogにLog.v()、Log.d()、Log.i()、Log.w()、Log.e()というログを書き込むメソッドがあります。 これらのメソッドは重大度レベルに対応しています。 - iOSのSwiftには、同様に重大度レベルに対応するメソッドを持つLoggerインターフェースがあります。
- Web向けのJavaScriptでは、標準的なロギングの方法は
- 現在のログAPIには、イベント属性を渡すための標準的な方法がありません。
- 補間文字列の引数をイベント属性を渡すパラメーターとして使うことができるかもしれません。
しかし、ロギングの仕様では、(引数を補間文字列に置き換えた後に得られる)人間が読めるメッセージを
LogRecordのBodyフィールドにマッピングしているようです。 - Log4jには、任意のキーバリューペアを持つ構造化されたメッセージを作成するために使える
EventLoggerインターフェースがありますが、log4jのメンテナーの1人によるこのStack Overflowのスレッドにあるように、log4jはAndroidで公式にサポートされていないため、Androidアプリで一般的に使われているわけではありません。 - Pythonでは、
logging.LogRecordのextraフィールドはOTelのLogRecordのattributesにマッピングされますが、このフィールドは隠れたフィールドであり、パブリックインターフェースの一部ではありません。
- 補間文字列の引数をイベント属性を渡すパラメーターとして使うことができるかもしれません。
しかし、ロギングの仕様では、(引数を補間文字列に置き換えた後に得られる)人間が読めるメッセージを
- 現在のログAPIには、
LogRecordのBodyフィールドにマッピングできるメッセージパラメーターがあります。 しかし、これは文字列のメッセージに限られており、構造化されたログを許容しません。
以上の理由から、イベントを作成するためのAPIが必要であると結論付けられます。
解説
イベントをログと区別するための構造を提案し、また、LogRecord データモデルを使ってイベントを作成する際にその構造が守られることを保証するAPIを持つことも提案します。
イベントの構造
すべてのイベントは名前とドメインを持ちます。 名前は必須(MANDATORY)です。 ドメインは、イベント名の衝突を回避するメカニズムとして機能するもので、任意(OPTIONAL)です。 この構造では、イベント名はドメインのコンテキストにおいてのみ一意になります。 これにより、異なるドメインにある2つのイベントが同じ名前を持ちながら、無関係なイベントであることが許容されます。 イベントにドメインが存在しない場合、イベント名の一意性については何も主張されません。
イベントとログのAPI
イベントとログを作成するためのAPIインターフェースを持つことも提案します。
現在、LogRecord を作成するための LoggerProvider と呼ばれるSDKしか存在しません。
しかし、OTelがログのためのAPIを持つべきかどうかという問題があります。 OTelコミュニティの一部は、すでに多くのロギングライブラリやAPIから選べる言語が存在しない場合を除いては、本格的なロギングAPIを持つべきではないと考えています。 さらに、一部の人気のあるロギングフレームワークが提供する豊富な設定オプションを持つことはできないので、OTelのロギングAPIは単なるもう一つのAPIになってしまうでしょう。 しかし、ログアペンダーAPIはイベントのためのAPIと非常に似ていることが指摘されたため、イベントのためのAPIとログアペンダーのためのAPIを別々に持つのではなく、イベントとログのための単一のAPIを持つことに合意し、ログのためのAPIはログアペンダーのみを対象とすることにしました。 これにより、トレースとメトリクスと一貫して、各シグナルに対して1つのAPIを持つことになります。
内部の詳細
イベント名とドメインは、セマンティック規約を使って定義される LogRecord の属性になります。
イベントとログのAPIは、トレースAPIと非常によく似たものになります。
TracerProviderとTracerに相当する、LoggerProviderとLoggerのインターフェースが存在することになります。
その後、Loggerインターフェースは、LogRecord データモデルを使ってイベントとログを作成するために使われます。
トレードオフと緩和策
APIのログ部分がエンドユーザーから呼び出し可能かどうかについて混乱が生じる可能性があります。 最終的には、人気のあるロギングライブラリを持たない言語で使用できるようになるかもしれませんが、他の人気のあるロギングライブラリやAPIが存在する言語での使用は推奨されず、この点はさまざまな場で強調されなければなりません。
先行技術と代替技術
クライアントサイドの計装については、当初、スパンが因果関係を提供するという利点を得るために、0-durationスパンを使ってイベントを表現することが提案されました。
たとえば、SplunkのRUM SDK for Androidは、0-durationスパンを使ってイベントを実装しています。
しかし、0-durationスパンは紛らわしく、LogRecord を使って表現される他のドメインの独立したイベントと一貫性がありません。
そのため、一貫性の観点から、あらゆる場所で独立したイベントに LogRecord を使うのが良いでしょう。
イベント間の因果関係をモデリングするという要件に対応するために、LogRecord にリンクされたラッパースパンを作成することができます。
未解決の問題
なし。
今後の課題
トレードオフと緩和策セクションで述べたように、人気のあるロギングライブラリを持たない言語では、エンドユーザーがこのAPIを使用できるようにすることが考えられます。- 将来、スパンイベントを
LogRecordを使って記録したくなる可能性があります。 この場合、それらはLogRecordのTraceIdフィールドとSpanIdフィールドを使ってスパンと関連付けられます。 これが望まれる場合、スパンイベントのためのLogRecordを作成するオプションをTracerProviderに追加することができます。