TT Lab
はじめる
学ぶ 学習パス コース

OTCA — OpenTelemetry認定アソシエイト

七つの計測器、ログレコードの十二フィールド、そしてスキーマ URL

TT Labで続きを見る

一言でいうと

メトリクスAPIの計測器(instrument)は、Counter・UpDownCounter・Gauge・Histogramと、その非同期(callback)の変形に分かれ、SDKが出力する合計(Sum)には、deltaまたはcumulativeというtemporalityが付きます。ログシグナルは、12個のフィールドを持つLogRecordデータモデルと、既存のロギングライブラリをそのモデルにつなぐブリッジAPIで構成されます。スキーマURLは、セマンティック規約が変わっても、コンシューマーがデータを解釈できるようにするバージョンの目印です。出典は、メトリクスAPI・メトリクスデータモデル・ログデータモデル・テレメトリスキーマのスペックです。

なぜ必要なのか

「リクエスト数」と「キューの長さ」と「室温」は、どれも数値ですが、性質が違います。リクエスト数は増えるだけで、キューの長さは増えたり減ったりし、温度は足し合わせて合計する意味がありません。計測器の種類が分かれている理由は、この性質をAPIの段階で明らかにしておくことで、SDKが適切なデフォルトの集計を選べるようにするためです。スペック概要はこれを「生の測定値を記録すれば、集計方法は最終ユーザーが選ぶ」と表現しています。

ログは逆方向の問題です。すでにすべての言語にロギングライブラリがあり、そのログを捨てて新しいAPIで書き直せとは言えません。そのため、ログシグナルは「既存のログをどう共通モデルに移し、トレースとつなぐか」から出発します。スキーマも同じ種類の問題です。セマンティック規約の属性名が変わると、その名前を期待していたダッシュボードとバックエンドが壊れます。

どう動くのか

計測器7種類

計測器は、名前・種類・単位(任意)・説明(任意)で定義されます。概念ドキュメントとAPIスペックの説明を合わせると、次のとおりです。

計測器 同期/非同期 性質 記録操作
Counter 同期 単調増加(増えるだけ) Add
Asynchronous Counter 非同期 単調増加、観測時点の累積値を報告 コールバック
UpDownCounter 同期 増加・減少の両方(キューの長さ) Add
Asynchronous UpDownCounter 非同期 足し合わせられる値(プロセスのヒープサイズ) コールバック
Gauge 同期 足し合わせられない値(騒音レベル)、変化したときに記録 Record
Asynchronous Gauge 非同期 足し合わせられない値(室温)、観測時点に報告 コールバック
Histogram 同期 分布に意味がある値(リクエストのレイテンシ) Record

同期計測器は、アプリケーションロジックの中で直接呼び出され、その測定値はContext(つまり現在のスパン)と関連付けられます。非同期計測器は、コールバックを登録しておくと、SDKが収集(collection)の時点にだけ呼び出します。スペックの例は、15秒ごとにセンサーの温度を読むことです。非同期の測定値はContextと関連付けられません。スペックは、ここでの同期・非同期がプログラミングのasyncパターンとは無関係だと付け加えています。UpDownCounterの説明には、「値が単調増加ならCounterを使うように」という指針があります。

各計測器にはデフォルトの集計があり、Viewで変更したり、特定の計測器を無視したり、報告する属性を選んだりできます。概念ドキュメントは、カーディナリティの限界も説明しています。メトリクスストリームあたりの固有の属性の組み合わせのデフォルトの上限は2000で、超えると測定値を捨てずに、otel.metric.overflow=trueという属性1つに折りたたみます。合計は合いますが、折りたたまれた測定値はほかの属性を失うため、その属性で絞り込むクエリは少なく数えます。

temporality: deltaとcumulative

データモデルでSumは、AggregationTemporality(deltaまたはcumulative)と、単調(monotonic)フラグ、そして開始・終了時刻を持つデータポイントで構成されます。deltaは、重ならない時間ウィンドウごとにその区間の値を報告し、cumulativeは、開始(通常はプロセスの開始)からの全体の合計を報告します。単調なdeltaの合計は負であってはならず、単調なcumulativeの合計は、以前の値より小さくなってはなりません。

スペックは、両者の間のトレードオフとして、プロセス再起動の検出、レート(rate)の計算、プッシュ/プル方式を挙げ、OTLPは両方をサポートし、必要ならDelta-to-CumulativeまたはCumulative-to-Deltaの変換を行えると書いています。ヒストグラムもtemporalityを持ち、minとmaxはdeltaのほうが有用で、delta→cumulativeの変換はできますが、その逆はできません。概念ドキュメントは、カーディナリティとの関係も指摘しています。deltaは周期ごとに状態を消すため、1周期の中の組み合わせだけを数えますが、cumulativeは状態を保持するため、一度上限に達するとプロセスが再起動するまで超過し続けます。Gaugeは特定の時刻のサンプル値なので、temporalityがありません。

ログレコードのフィールド

ログデータモデルは、「既存のログ形式を曖昧さなくこのモデルに移せる必要がある」という要件から出発し、よく使われるフィールドは名前付きの最上位フィールドに、残りはAttributesに置きます。最上位フィールドは12個です。

フィールド 意味
Timestamp イベントが発生した時刻(元の時計)
ObservedTimestamp 収集システムが観察した時刻。SDKで作ったログではTimestampと同じ
TraceId / SpanId / TraceFlags W3C Trace Contextの識別子とフラグ。リクエスト処理中のログに付く
SeverityText 元のログレベルの文字列
SeverityNumber 正規化された深刻度の数値
Body 本文
Resource ログを出したエンティティ
InstrumentationScope ログを出した範囲(ライブラリ・モジュール)
Attributes 追加情報
EventName イベントの種類を表す名前

時刻が1つしかサポートされない場所へ移すときは、Timestampがあればそれを、なければObservedTimestampを使います。SeverityNumberは1–24の範囲で、1–4がTRACE、5–8がDEBUG、9–12がINFO、13–16がWARN、17–20がERROR、21–24がFATALで、0は未指定です。スペックの例のように、17は20より深刻度の低いエラーです。EventsはこのLogRecordの標準化された形式で、ログのセマンティック規約はEvent形式で定義されます。

ブリッジAPI

ログAPIは、最初の文で対象を明らかにしています。「ロギングライブラリの作者がログアペンダー(appender)を作れるように提供され、アペンダーはこのAPIで、既存のロギングライブラリとOpenTelemetryのログデータモデルの間をつなぐ」という内容です。構造は、LoggerProviderからLoggerを取得し(name、必要に応じてversion・schema_url・attributes)、LoggerがLogRecordをEmitするだけです。Emitは、Timestamp・ObservedTimestamp・Context・SeverityNumber・SeverityText・Body・Attributes・EventNameを受け取り、Contextを渡さなければ現在のContextを使います。これが、ログにTraceId・SpanIdが自動で付く仕組みです。Enabledは、LogRecordを作るコストが大きいときにだけ先に問い合わせる最適化で、呼び出しは必須ではありません。

ログシグナルの概要は、収集方式を2つに分けます。ファイルや標準出力に出たログをCollectorが読んでパースする方式(アプリケーションの変更はほとんどありませんが、パースが難しくなります)と、アペンダーを付けてOTLPで直接送る方式(構造化がうまくでき、ファイル・ローテーション・パースがなくなりますが、OTLPを受け取る宛先が必要です)です。どちらの場合も、アプリケーション開発者は、起動時にアペンダーとSDKを設定するだけで済みます。

スキーマURL

セマンティック規約は進化し、テレメトリのソースとコンシューマーは、それぞれ違う速度で変わります。スキーマスペックは、この3者を切り離すために、次のことを定めています。スキーマにはバージョンがあり(MAJOR.MINOR.PATCH)、バージョン間の変換(例: 属性名の変更)をスキーマファイルが明示し、各バージョンは固有のSchema URLで識別されます。URLの最後のパスがバージョンで、その前がスキーマファミリーです。スキーマファイルは発行されると不変なので、永続的にキャッシュしてもかまいません。ソース(計装ライブラリ)は、出力するテレメトリにスキーマURLを入れ(Tracer・Meter・Loggerを取得するときのschema_url引数がその位置です)、コンシューマーは、受け取ったスキーマバージョンを見て、必要なら自分が期待するバージョンに変換します。ドキュメントの例は、1.2.0のdeployment.environmentを、1.1.0を期待するバックエンドがenvironmentに変えて保存するというもので、バックエンドがスキーマを知らない場合は、Collectorのスキーマ変換プロセッサーが代わりに行います。OpenTelemetry自体のスキーマは、/schemas/<version>のパスで発行されます。

現場での姿

あるチームが「現在アクティブな接続数」をCounterで記録していたため、グラフが上がるだけでした。接続が閉じられるときに減る必要があるので、UpDownCounterが正しい選択で、実際には接続プールから値を読むコールバックのほうが自然なため、Asynchronous UpDownCounterに変更しました。

別のチームは、Prometheusに送るメトリクスとベンダーのバックエンドに送るメトリクスの値が違うと言っていましたが、片方はcumulative、もう片方はdeltaに設定されていました。同じCounterでも、temporalityが違うと、1つの点の数字が意味する区間が違います。

次のクイズで確認すること

クイズでは、APIとSDKを分離した理由、Batching processorのデフォルト値、同期計測器と非同期計測器の違いとContextとの関連付け、deltaとcumulativeの意味、LogRecordのTimestampとObservedTimestamp、ブリッジAPIの対象、スキーマURLの役割を問います。