OpenTelemetryにおけるエラー処理
この記事は英語の原文を日本語に翻訳したものです。原文: https://opentelemetry.io/docs/specs/otel/error-handling/
翻訳元: open-telemetry/opentelemetry-specification v1.60.0(コミット 29ae8c7)
OpenTelemetryは、ユーザーがアプリケーションコードを監視できるようにテレメトリーデータを生成します。多くの場合、ライブラリが行う処理はアプリケーションのビジネスロジックの観点からは必須ではありません。ユーザーは、計装対象のアプリケーションの挙動をライブラリが大きく変えてしまうよりも、テレメトリーデータが失われることを望むだろうという前提に立っています。
OpenTelemetryは、プラットフォームの拡張機構を介して有効化されたり、実行時に動的にロードされたりする場合があります。そのため、エンドユーザーにとってライブラリの利用が自明でなくなり、アプリケーション開発者の制御が及ばない場合すらあります。この事情が、エラー処理に関して独自の要求事項を生じさせます。
エラー処理の基本原則
OpenTelemetryの実装は、実行時に未処理の例外をスローしてはなりません(MUST NOT)。
- APIメソッドは、エンドユーザーによる誤った使用があっても未処理の例外をスローしてはなりません(MUST NOT)。APIとSDKは、欠落した引数や無効な引数に対して安全なデフォルト値を提供するものとします(SHOULD)。例えば、
Span構築時にスパン名の引数としてnullが渡された場合、emptyのような名前を使うことが考えられます。 - APIまたはSDKは、不正なユーザー設定や環境などを理由に、初期化時にアプリケーションを失敗させる形で早期に失敗してもよい(MAY)ものとします(fail fast)。ただし、Collectorから受け取った動的な設定変更などを理由に、後から実行時にアプリケーションを失敗させてはなりません(MUST NOT)。
- SDKは、自身の処理内で発生したエラーについて未処理の例外をスローしてはなりません(MUST NOT)。例えば、エクスポーターは、テレメトリーデータの送信先エンドポイントに到達できない場合でも例外をスローすべきではありません。
ガイダンス
- 外部のコールバックを受け取るAPIメソッドは、すべてのエラーを処理しなければなりません(MUST)。
- バックグラウンドタスク(スレッド、非同期タスク、生成されたプロセスなど)は、例外がエンドユーザーのアプリケーションに影響しないよう、グローバルなエラーハンドラーのコンテキスト内で実行すべきです。
- 長時間実行されるバックグラウンドタスクは、内部エラーによって永続的に失敗するべきではありません。一般に、内部の例外は、その例外を引き起こしたリクエストの実行コンテキストにのみ影響を及ぼすべきです。
- 内部のエラー処理は、言語固有の慣習に従うべきです。一般に、開発者はエラーハンドラーの適用範囲を最小限にとどめ、予期される例外に対しては個別の処理を追加すべきです。
- 外部のコールバックやオーバーライド可能なインターフェースには注意してください。これらは例外をスローするものと想定すべきです。
- APIやSDKのユーザーがコールバックとして明示的に提供したわけではないメソッドを呼び出す際にも注意してください。例えば、SDKがユーザーのオブジェクトに対して呼び出すことのある
ToStringメソッドは、実装が悪く、スタックオーバーフローを引き起こす場合があります。アプリケーション自体がこのメソッドを一度も呼び出さないことは珍しくなく、そのためこの不完全な実装がアプリケーション所有者に検出されないまま残ることもよくあります。 - APIの呼び出しが非
nullの値を返すことが期待されている場合、処理ロジック内でエラーが発生したときでも、SDKは「no-op」オブジェクトや、あらかじめ確保され利用可能な「デフォルト」オブジェクトを返さなければなりません(MUST)。これにより、API呼び出し箇所はnullオブジェクトのメソッドやプロパティへのアクセスによってクラッシュすることがなくなります。
エラー処理とパフォーマンス
エラー処理や広範な入力検証は、パフォーマンスの劣化を引き起こす場合があります。特に、入力オブジェクトの型がコンパイル時に保証されない動的言語ではその影響が大きくなります。実行時の型チェックはパフォーマンスに影響し、しかもエラーが起きやすいため、最善を尽くしても例外が発生することがあります。
例外がユーザーコードに漏れ出さないことを保証する、グローバルな例外処理ロジックを持つことが推奨されます。そして、エラー発生時の挙動やSDKエラーのトラブルシューティングを向上させる型チェックの網羅性と、SDKのパフォーマンスとの間で、妥当なトレードオフを選択してください。
自己診断
API、SDK、エクスポーター、計装などを含むすべてのOpenTelemetryライブラリは、自己診断用のメトリクス、スパン、その他のテレメトリーを提供することが推奨されます。これらは容易に有効化でき、また既定でフィルタリングして除外できるようにします。
このようなテレメトリーの好例として、エクスポーターがテレメトリーのアップロードに要した時間を示すSpanエクスポーターが挙げられます。別の例としては、アップロード待ちのテレメトリーデータの現在のキューサイズを表す、SpanProcessorが公開するメトリクスが考えられます。
ライブラリが、本来ユーザーに公開されるはずだったエラーを抑制する場合、そのライブラリは言語固有の慣習に従ってエラーをログに記録するものとします(SHOULD)。SDKは、エンドユーザーがアプリケーションコードとは別に自己診断を処理できるよう、コールバックを公開してもよい(MAY)ものとします。
SDKが自身の挙動について発するテレメトリーに関するガイダンス(SDKの自己オブザーバビリティメトリクスを含む)については、自己オブザーバビリティを参照してください。
エラーハンドラーの設定
SDKの実装は、関連するエラーについてライブラリの既定のエラー処理の挙動をエンドユーザーが変更できるようにしなければなりません(MUST)。アプリケーション開発者は、APIの誤った使用や不正な設定を捕捉するために、ステージング環境では厳格なエラー処理で実行したいと考える場合があります。このようにカスタムのエラーハンドラーを設定することが、上記で説明した基本的なエラー処理の原則に対する唯一の例外であることに注意してください。エンドユーザーがカスタムのエラーハンドラーを設定・登録する仕組みは、言語固有の慣習に従うべきです。
例
以下は、エンドユーザーがカスタムのエラーハンドラーを登録する方法の例です。これらの例はあくまで説明のためのものです。OpenTelemetryクライアントの作者は、その設計が上記の要求事項を満たしている限り、これらの例から自由に変えて構いません。
Go
// The basic Error Handler interface
type ErrorHandler interface {
Handle(err error)
}
func Handler() ErrorHandler
func SetHandler(handler ErrorHandler)
// Registering a custom Error Handler
type IgnoreExporterErrorsHandler struct{}
func (IgnoreExporterErrorsHandler) Handle(err error) {
switch err.(type) {
case *SpanExporterError:
default:
fmt.Println(err)
}
}
func main() {
// Other setup ...
opentelemetrysdk.SetHandler(IgnoreExporterErrorsHandler{})
}
Java
OpenTelemetry Javaは、エラーを含むすべてのログの出力と処理にjava.util.loggingを使用します。カスタムのハンドラーやフィルターは、コード内でも、Javaのログ設定ファイルを使っても登録できます。
## Turn off all error logging
io.opentelemetry.level = OFF
// Creating a custom filter which does not log errors that come from the exporter
public class IgnoreExportErrorsFilter implements Filter {
public boolean isLoggable(LogRecord record) {
return !record.getMessage().contains("Exception thrown by the export");
}
}
## Registering the custom filter on the BatchSpanProcessor
io.opentelemetry.sdk.trace.export.BatchSpanProcessor = io.opentelemetry.extensions.logging.IgnoreExportErrorsFilter