ねののお庭。

かりかりもふもふ。

Re:ゼロから始める Observability

実際のところ Observability とは一体なんなのでしょうか? 「Observability とは?」と自問自答した際、どのような回答が内から生じるでしょうか? わりとボヤけた答えしか返ってこなかったりしませんか? という事で、この記事ではその問に対して明確に返せるようになるために、Observability とはそもそもなんぞやというのを深ぼっていきたいと思います。 そして Observability を高めるための考え方と、C# でどう実践していけば良いかについても言及します。 尚、多分に私見が含まれますのでその点についてはご注意ください。

ちなみに何故タイトルに Re を入れているかというと、当然某名作インスパイアです...というだけではなく、過去に以下のようなブログ記事で W3C Trace Context やらログやら OpenTelemetry のお話をしているのですが、 それらは結局 Observability と地続きなので Re とつけてもいいでしょう!というノリでつけています。

いろいろ理由こじつけてるけど、「ゼロから始める」という文字列には「Re:」をつけたくなってしまうだけかもしれません。

登壇版

.NET ラボ 2024/05/25 で登壇した際の資料です。 スライドはこちら

What is Observability?

とりあえず「What is Observability?」でぐぐってみてください。非常に多くの会社が各社思い思いの Observability について語っています。 以下で示しているのはごく一部にすぎません。

What is Observability の検索結果

読み比べたりするといろいろ面白かったりするのですが、各社共通で言っている事は何かというと「Observability とはシステムの出力から内部の状態を理解できるようにする事だよ」です。 ここで「あれ?ちょっと待てよ?」となります。 Observability 云々以前に、我々は日常的にログというやつを出力しています。 そしてそのログの目的はなんでしょうか?それは「ソフトウェアの振る舞いをログから理解できるようにする事」です。 このあたりは過去の記事でいろいろ語っているので気になる方は是非。 ともあれ見やすく整理すると以下のようになります。

  • Observability とは (各社共通)
    • システムの出力から内部の状態を理解できるようにする事
  • ログの目的
    • ソフトウェアの振る舞いをログから理解できるようにする事

Observability とはつまりログなのではないか...? という気がしてきますね。

ここで少し、ソフトウェアの振る舞いをログから理解できるようにするためには、どのような情報がログに含まれている必要があるかについて触れておきたいと思います。 ソフトウェアの振る舞いを理解可能にする理想的なログには以下のような情報が含まれている事が望ましいです。

(赤字)は Microsoft.Extensions.Logging との対応

これらが適切な粒度でログに出力されていれば、ソフトウェアの振る舞いを理解するには十分な情報量だと思います。 なお、図中において赤字で書いている文字列は Microsoft.Extensions.Logging が JSON 形式で構造化ログを出力するときのプロパティに対応するようになっています。

図中の橙色で囲われている上の4つ、いつ、どのコンポーネントが、どの論理操作の中で、どういう親子関係で、というのはライブラリである Microsoft.Extensions.Logging が面倒を見てくれます。ちなみに TraceId と SpanId は Microsoft.Extensions.Logging 内部の ActivityLogScope という private class が LoggerFactoryScopeProvider という internal sealed class の中に存在し、そのあたりが責務を負っています。 そのため ASP.NET Core に限らず Worker Service 等でも Microsoft.Extensions.Logging を使っていれば TraceId と SpanId は簡単にログに出力出来るようになっています。

緑で囲われている、どういうコンテキスト、どういうパラメータで、何をして、どの程度の重要度なのか、というはソフトウェアの開発者がいろいろと考える必要があります。 特に Scope と State にどういう情報を突っ込まば適切なのか、というのは色々考える必要があります。 Scope ついてはこちらをどうぞ。

青で囲っている下二つは Microsoft.Extensions.Logging が特別に何かをサポートしているわけではないので実装次第ですが、どういう結果で終わってどの程度時間がかかったのかは、出力しておくと便利だったりします。 例えば ASP.NET Core の HttpLogging ではこういった duration や response の status code 等をログに出力してくれますし、 AspNetCore.SignalR.OpenTelemetry も duration に関しては出力するようにしています。

さて、このような理想的ログが適切な粒度で出力されていれば Observability があると言えるでしょうか? (適切な粒度というのがなかなか難しいワケですが...。)

これはごく個人的な見解ですが、おそらくそれは半分は Observability があるといっても差支えがないと思っています。 何故かというと、所謂 Observability 3本柱というやつはログ、トレース、メトリクスを指しているわけですが、この3つは所詮ログを役割事に細分化したに過ぎず、広義には全てログです。 そのため理想的ログが適切な粒度で出力されていれば Observability 3本柱を表現しているわけなので、十分 Observability があるといっても過言ではない気がするのですが、とはいえ半分でしょう、というのが個人的な見解です。

じゃぁもう半分はなんなのよ、という話なのですが、 そのもう半分がなにかを特定するためには Observability が声高に重要だと叫ばれ始めた根っこの理由をちゃんと理解する必要があります。 なのでそれを再び(Re:)ゼロから見ていきましょう。

Observability がなぜ重要視され始めたか。

ぶっちゃけ理由は一つで、現代のソフトウェアは大規模かつ複雑になった(なってしまった)からです。 分散アーキテクチャ、マイクロサービス、Cloud Native 系技術等が広く用いられるようになり、モノリスの時代に比べたら非常に多くの様々なものが疎結合かつ動的になり複雑度が増しました。 そしてその結果、発生する問題も複雑になり「従来の問題解決手法では発生する問題の解決が困難である」という事態が生じ、この課題を解決したくて Observability というやつが重要であると声高に叫ばれ始めたわけです。

ソフトウェアが大規模かつ複雑になった結果、どういう問題が発生したかというと概ね以下通り。

  • 予期できていない問題が発生する
  • 再現が困難、あるいは不可能
  • エスパー/直感による問題解決の限界

それそれの問題について見ていきましょう。

予期できていない問題が発生する

この予期できない問題というのは「遅い・重い」といった問題から「使えない」みたいな問題まで様々なレンジのものを含みます。

一昔前であれば、静的で単純な構成だったので問題になりうる点をある程度予期できたりしたのですが、 現代は動的で複雑な構成になったためどこで問題が発生するか、というのは非常に予期しづらくなりました。 もう少し付け加えると、昔と違って予期しやすい問題(特に計算リソース的な問題)はクラウドを使っていれば事前に対策可能な時代になりました。 ただしその代わりに、より難しい予期しづらい問題が起きるようになりました。 またクラウド上で動かしているという事は当然クラウドベンダー側の都合にも影響される部分もあるので、いろんな意味で予期は難しいのです。

このような Unknown unknowns (知らない事を知らない事) が発生した場合でも対策可能なようにしていく必要がある、というのが Observability を高めていくモチベーションの一つです。 ちなみに Unknown unknowns という語彙は各社の what is observability を読んでいると頻発する語彙だったりします。

再現が困難、あるいは不可能

先ほども書いた通り、現代の本番環境というのは極めて大規模かつ動的かつ複雑です。 そして当然ながら本番環境の実行環境は単体テストやステージング環境等の比較的クリーンな実行環境とは全く異なります。 そのため単体テストやステージング環境で動作確認している分には問題が起きなくとも、本番環境でのみ起きる問題が存在しえます。

この本番環境でのみ起きる問題というのは非常に厄介です。 開発環境で問題の再現が容易な場合は「手元で問題を再現し、修正する」という王道のプロセスをたどって問題の解決を図れます。 しかしそのような王道の問題解決プロセスは本番環境でのみ起きる問題に対して通用しません。

とはいえ発生した問題に対しては何が問題であったかを明らかにし、対応が必要かの是非を判断し、改善が必要な場合は改善を積み重ねなければ安定したソフトウェアの運営には程遠いです。 なので判断材料等は絶対に必要なわけですが、問題の再現ができないという事は多くの場合何が問題の原因だったか分からないという事でもあり、それでは修正のしようがありません。 頑張って修正しようとしても調査に多大な時間がかかり、新機能の開発等が滞ってしまうなど無視できない悪影響が出てきてしまいます。

そのため本番環境が出力するテレメトリデータから発生した問題は何が原因であったかを明らかにできる必要があります。

エスパー/直感による問題解決の限界

問題が起きた際に「直接的原因はログに書かれてはいないが、恐らくあのあたりが問題なんだろうな」みたいな数段飛ばしの「知識と経験に基づく予測」を行って問題解決をしていくのが割と業界全体として行われている事です。 自分はこれをよくエスパーといっていますが、現代においてエスパーによる問題解決はなかなか限界に来ています。 規模が大きくなれば当然ブラックボックスは増えますし、エスパーに頼っていると問題が発生した際に古参しか対応できない状態に陥ります。

これは1つのサービスを複数チームで開発していると顕著かと思います。 マイクロサービスのようなアーキテクチャを採用する最大の理由は技術的にどうのこうのという理由ではなく、大規模な組織においても機動力を確保するためです(むしろそうじゃないならマイクロサービスなんてやめておいた方がいいまである)。 そのため多くの組織で所謂コンポーネントチームみたいなチームの切り方しているかと思います。 そのようなチームの切り方をしている場合、よそのチームが開発しているマイクロサービスがコードレベルでどうなっているかというのは、殆どの開発者にとっては知ったことではありません。 そのためどのように作られているかについての知識はありませんから、当然エスパーも直感も働きません。

コンポーネントチームではなく、フィーチャーチームみたいな形でチームを切っていたとしても、どちらにせよ機能追加が加速すればするほど、ブラックボックスは増え続けるのでエスパーは徐々に働かなくなっていきます。

Observability の目指すところ

結局 Observability の目指すところはどこなんだよ、というと大きなところは以下の2つでしょう。

  • 予期できていない問題が発生した「後」にコードの変更なく問題の原因究明が可能な事
  • エスパー/直感に頼らず問題解決ができる事

この「問題が発生した「後」にコードの変更なく」というのが重要です。 問題が発生した後に原因究明のためにログを差し込んだところで、再現性の観点から現代のソフトウェアにおいては手遅れです。 問題が発生した際のテレメトリデータからその原因が分からければ、未知の問題が発生した際に太刀打ちできませんからね。

そしてこの「後」にというのは Observability と Monitoring (監視) における一番の違いでもあります。 従来の監視は問題が発生する「前」に発生しうる問題を予期してアラート等を仕掛けるわけですが、それでは Observability が立ち向かいたい課題である「未知の予期できない問題」の解決の役には立たないのです。

OpenTelemetry 関連用語

Observability の目指すところが明確になった所で「Observability を高めていくには実際どうすればいいのか?」という話に入りたいのですが、その話の前に OpenTelemetry 関連用語にすこし紹介したいと思います。

W3C Trace Context

Trace や Span といった用語が分からない場合は「ASP.NET Core と W3C Trace Context とお手軽ロギング。」という過去記事をご覧ください。

Attribute

OpenTelemetry の用語として Attribute というのが存在します。 この Attribute というのは key-value pair であり、OpenTelemetry におけるデータモデルの様々なところで用いられています。 例えば Span には任意の情報をいろいろ詰め込めるわけですが、それらは Attribute として詰め込まれます。

型的には以下のような感じ。

  • Key MUST be a non-null and non-empty string.
  • Value is either:
    • A primitive type: string, boolean, double (IEEE 754-1985) or signed 64 bit integer.
    • An array of primitive type values.

上記の通り Attribute の key の型は string なのですが、lowercase 推奨なので気を付けましょう。 また HTTP/RPC/Database/Exception等の広く用いられる技術の attribute に関しては、どういう key 名を使うか、その key に対する value の型は何であるべきか、等の規則(semantic conventions)が決まっています。 基本的にはそれら規則に則りましょう。とはいえ HTTP や RPC 等はだいたいフレームワークが attribute を仕込んでくれるので自分で書くことあるかというと微妙なところですが、足りないものを付与したりしたくなる事はあるので、そういうときは semantic conventions の方でなにか規則がないかを確認する事をお勧めします。

Trace 関係用語

OpenTelemetry の仕様において Span を表現するオブジェクトには IsRecording というフィールドが存在する事定められています。 この IsRecording は Span に対して Attribute 等のデータを記録・保存できるか否かを示すフラグで、false の場合は Attribute 等を保存できません。 また SpanContext には Sampled というフラグが定義されている事が定められていますが、これは W3C Trace Context の trace-flags の sampled に相当するものです。

  • IsRecording
    • Span のフィールド
    • false の場合 span に attribute 等のデータを保存できない
  • Sampled
    • SpanContext のフラグ
    • W3C Trace Context の trace-flags: sampled に相当

より詳細はこちらを参照してください。

C# における Span の表現

C# において Span がどう表現されているかというと、BCL においては System.Diagnostics.Activity、 OpenTelemetry.Api においては OpenTelemetry.Trace.TelemetrySpan というクラスで表現されています。 なお TelemetrySpan 自体は Activity の薄いラッパーでしかないので、Span に対して Attribute を追加したりする際には基本的に Activity を直接いじれば OK です。

注意するべき事として、OpenTelemetry の語彙と C# の Activity での語彙が一致しないというのがあります。 特に record という語彙はなかなか厄介で、OpenTelemetry における IsRecording に相当するのは Activity.IsAllDataRequested というプロパティです。 Activity.Recorded というプロパティがあるのですが、これは W3C Trace Context の sampled に相当します。 非常にややこしいですね。

また Span に対して attribute を設定したい場合は、Activity.SetTag() を用います。 Activity における tag という言葉は OpenTelemetry における attribute に相当します。 そしてこの attribute を set する前には、IsAllDataRequested 、つまり OpenTelemetry における IsRecording が true かをチェックする必要があります。

var activity = Activity.Current;

_ = activity?.IsAllDataRequested; // OpenTelemetry の IsRecording に相当
_ = activity?.Recorded; // W3C Trace Context の trace-flags: sampled に相当

// attribute を設定する前に IsAllDataRequested が true か確認
if (activity is not null && activity.IsAllDataRequested)
{
    // Activity.SetTag() は TelemetrySpan.SetAttribute() に相当
    activity.SetTag("key", "value");
}

Observability を高めていく

各種用語も抑えたので、Observability を高めていくためには何をしたらいいかを考えていきましょう。 ここで少し Observability の目指すところをおさらいしておきます。

  • 予期できていない問題が発生した「後」にコードの変更なく問題の原因究明が可能な事
  • エスパー/直感に頼らず問題解決ができる事

このような Observability を実現するための必須の要素はなんでしょうか?

それはテレメトリデータと分析です。

収集したテレメトリデータは非常に大きな集合ですから、それらをいかに分析し、問題の発見・究明・解決に近づけるかが肝です。 Observability 3本柱などと言われているものはテレメトリデータのデータ型3本柱という事に過ぎないので、あまりそのあたりに惑わされずテレメトリデータと分析の2本柱が大事だよ、と思っておいた方が何かと身のためかと思います。

テレメトリデータに関する仕様・規則・ライブラリ提供等が OpenTelemetry の責務であり、分析については Observability backend の責務です。

この記事では問題の発見・究明・解決のためにはどのような分析機能が Observability backend に存在すると嬉しいかという点については触れませんが、 有用な分析をするための大前提として、テレメトリデータ に十分な情報量が詰め込まれている必要があります。 そのためまずは十分な情報量が詰め込まれているテレメトリデータを得るにはどうしたらよいかを考える必要があります。

まずテレメトリデータを取得するために初手にやることといえば、当然 OpenTelemetry と様々な計装ライブラリを導入することでしょう。 それらを導入することで非常に多くのテレメトリデータをお手軽に取得できるようになります。 しかしながら計装ライブラリを導入するだけで Observability が目指している目標が達成できるだけの十分な情報量が含まれたテレメトリデータを得る事は出来るでしょうか?それは間違いなく No でしょう。

では十分な情報量を得るにはどうすればよいでしょうか? そもそも十分な情報量とは何かですが、それはソフトウェアの振る舞いを理解する事ができるだけの情報量です。 そしてそれは理想的なログに含まれる情報量でもあり、それは以下のように整理できるのでした。

再掲

なので テレメトリデータ、特に Span においても同等の情報量が必要です。 Span における情報量について整理すると、以下のようになります。

(赤字)は OpenTelemetry との対応

ライブラリがやってくれることとしては、timestamp, trace_id, span_id, duration の4つです。

実装次第なところについては、何をして、どのような結果でおわったか、の2点です。 「何をして」の name については自分で Span を切る場合は設定が必要なのですが、フレームワーク等が切った Span に対して attribute 等を設定するだけであれば特に考える必要はありません。 またどういう結果で終わったか、というのは attribute に otel.status_code という key で設定します。 この otel.status_code は OK/Error の2値をとる optional なものなのですが、自分で Span を切った場合は設定しておくと良いでしょう。 フレームワークやライブラリが Span を切っている場合は良しなに設定してくれたりする場合もあります。

自分で考える必要があるものに関しては、どういうコンテキスト、どういうパラメータで、という2点です。 これらは自分で考えて attribute に詰めていくことになります。 そうでないと Observability の目標は達成できませんし、情報量としてもログ以下という事になります。 なお、どのようなコンテキストやパラメータを含めると良いか?というと「Cardinality の高い情報」というのが Observability 界隈ではよく言われている話です。

そもそも Cardinality というのはなによ、honeycomb という Observability backend を開発している会社の記事には以下のように書かれています

Cardinality is simply a reflection of an attribute’s uniqueness. High-cardinality data contains a lot of very specific values, while low-cardinality data has only a few.

具体的には Cardinality の高い情報とは user-id や email addresses といった一意な情報です。 一方で Cardinality の低い情報とは geo-location や cloud providers などの取りうる値が少ない情報です。

この Cardinality が高い情報は、問題がどういう状況で起きたのかを多く示唆してくれるため問題解決を容易にしてくれます。 ではどうやって Attribute を追加するかというと、Activity.SetTag() を使いましょう。

// attribute を設定する前に IsAllDataRequested が true か確認
if (activity is not null && activity.IsAllDataRequested)
{
    // Activity.SetTag() は TelemetrySpan.SetAttribute() に相当
    activity.SetTag("key", "value");
}

コンテキストとしては user-id, org-id, plan-type, 等のものを設定するとよいのではないでしょうか。 パラメータとしては、操作対象の entity の id 等、操作する際に用いる value などを追加すると良いのではないかなと思います。

これはログと Span の双方に言えることですが、コンテキスト情報をテレメトリデータに加える際には Middleware や Filter を活用すると非常に効率的です。 ログの場合はログのスコープに、Span の場合は attribute にコンテキスト情報を Middleware や Filter で追加する事で、コンテキスト情報の追加漏れを防ぐ事ができ、また entity を操作をしている service 層等のコードでは entity の id や操作する際に用いる value などを attribute に設定する事に専念できます。

なお追加した attribute をどう生かすの?というお話がありますが、それは Observability backend 次第なので Observability backend を選定する際には attribute をどういう風に分析に生かせるか?という点を念頭において選定するといいんじゃないかかと思います。

また、自前で Span を切りたい場合は ActivitySource を使いましょう。使い方は簡単。 以下は AspNetCore.SignalR.OpenTelemetry から引用しているコードなのですが、まず ActivitySource を static で new してあげます。 そして ActivitySource.CreateActivity() で新しい Activity を作成し、Activity.SetTag() を用いて Create された Activity に様々な attribute を付与し、情報量を高めていきます。そして Activity.Start() して return します。ちなみに ActivitySource.StartActivity() というメソッドを使えば一発で開始済みの Activity を作成することもできます。 ちなみに以下は RPC の semantic conventions にしっかりと則っています。

internal static class HubActivitySource
{
    internal const string Name = "AspNetCore.SignalR.OpenTelemetry";

    private static readonly ActivitySource ActivitySource = new(Name, typeof(HubActivitySource).Assembly.GetPackageVersion());

    internal static Activity? StartInvocationActivity(string hubName, string methodName, string? address)
    {
        var activity = ActivitySource.CreateActivity($"{hubName}/{methodName}", ActivityKind.Server);

        if (activity is null || activity.IsAllDataRequested == false)
        {
            return null;
        }

        activity.SetTag("rpc.system", "signalr");
        activity.SetTag("rpc.service", hubName);
        activity.SetTag("rpc.method", methodName);

        if (!string.IsNullOrEmpty(address))
        {
            activity.SetTag("server.address", address);
        }

        return activity.Start();
    }

    // 略
}

これを実際に Span を切りたい所で Activity を start するようにメソッドを呼びます。 スコープを出たら Activity.Dispose() が呼び出されるように、using statement や using declaration を用いましょう。

ちなみに Filter 等では以下のようにログのスコープも追加してあげるとなお良しです。

public sealed class HubInstrumentationFilter : IHubFilter
{
    public async ValueTask<object?> InvokeMethodAsync(...)
    {
        // 略

        // Filter で
        //     ログのスコープを追加したり
        using var scope = HubLogger.BeginHubMethodInvocationScope(_logger, hubName, methodName);
        //     Span を切ったり
        //     Attribute を追加したり
        using var activity = HubActivitySource.StartInvocationActivity(hubName, methodName, address);
        
        // 略
    }
}

最後に OpenTelemetry と Activity を繋げる必要があるので、そのために TraceProviderBuilder.AddSource()ActivitySource を作ったときの名前を渡してあげる必要があります。 忘れないようにしましょう。これを忘れると ActivitySource.CreateActivity() をいくら叩いても null しか返ってきませんし、Span は切られたことになりません。 ちなみに内部的には ActivityListener を用いて OpenTelemetry が Activity の振る舞いのイベントを購読するようになっています。

public static class TracerProviderBuilderExtensions
{
    public static TracerProviderBuilder AddSignalRInstrumentation(this TracerProviderBuilder builder)
    {
        return builder.AddSource(HubActivitySource.Name);
    }
}

OpenTelemetry Collector の活用

次は OpenTelemetry Collector について。 そもそも OpenTelemetry Collector は何者かというと、テレメトリデータ の proxy です。

Collector がない場合は Application は直接テレメトリデータを Observability backend に送信します。

Collector なし

それに対して Collector を導入すると以下のように、アプリケーションから生まれたテレメトリデータは全て Collector を経由して Observability backend に送信されるする事になります。Collector を通す事で、アプリケーションから送信されたテレメトリデータを様々な形で捏ねる事ができます。

Collector あり

この Collector は不要かどうかという話がちらほらあるのですが、事実上ほぼ必須のものだと個人的には感じています。 それには以下のような理由があります。

  • 不要なトレースはサンプリングしたくない
  • 不要な情報は取り除きたい
  • Attribute を semantic conventions に統一したい

それそれについて見ていきましょう。

不要なトレースはサンプリングしたくない

恐らく Collector に対する一番の需要はコレでしょう。 不要なトレースを Observability backend に送りつけると Observability backend に対して決して安くない無駄な課金が発生しますからね、不要なものはそもそも送りたくない(サンプリングしたくない)のです。

不要なトレースにもいろいろあります。 一番わかりやすいのは health check 等のノイズでしかないトレースです。 またサービスが大規模であればあるほど、実際のところ正常なトレースの多くは殆ど似たようなものなので不要だったりします。 もちろん Observability の目的を達成するためには、問題が発生しているトレースが正常なトレースと何が違うかを比較可能にしておく必要があるため、正常なトレースの全てが不要なわけではありません。 しかし正常なトレースの全てを Observability backend に送信する必要はないので、サンプリングして間引きたくなります。 ただしこのあたりのサンプリング戦略は現状これが最強!みたいなものは無いので、サービス毎の最適解を見つける必要があります。

このような需要にこたえるため、OpenTelemetry Collector には Tail Sampling Processor という Processor が存在し、必要なトレースのみをサンプリングする事ができます。

ちなみに、Tail sampling の対になる語彙は Head sampling です。 一通り処理が終わった後にサンプリングするか否かを決定するのが Tail sampling で、 トレースが始まった時点でそのトレースをサンプリングするか否かが決まるのが Head sampling です。 実用上は、そのトレースが問題が起きたトレースなのかは一連の操作が終わるまで分からないため、ほとんどの場合で tail sampling をする事になると思います。

不要な情報は取り除きたい

不要な情報とは何かですが、例えば個人情報や HTTP のクエリ等です。 Observability backend は全ての開発者と様々なステークホルダーが触る(べき)ものです。 そのため email address 等のような個人情報がテレメトリデータに含まれており、Observability backend から簡単に見えてしまうのは当然許されません。 また HTTP のクエリにも様々な個人情報や token 等が含まれている場合があり、これも抜かないとマズイです。 ちなみ SignalR 認証かけて使っていると WebSocket の接続の都合上、HTTP のクエリに JWT token が付与されるので絶対抜かないとヤバイ。

こういった自分たちにとって不要な情報 (attribute) を抜くためのオプションは殆どの場合において計装ライブラリ側に存在していません。問答無用で多くの情報が付与されます。そのため Collector で抜くしかなありません。

OpenTelemetry Collector では Attributes Processor という Processor を用いる事で、Attribute を捏ねる事ができます。これを用いる事で不要な attribute を削除したりする事が可能です。

Attribute を semantic conventions に統一したい

OpenTelemetry の最大のメリットの一つは specification と semantic conventions が決まっているため、様々な言語・ライブラリ・フレームワークで開発していても統一されたテレメトリデータが得られる事です。統一されている事で Observability backend において各マイクロサービスに対し、横断的かつ統一的な query が記述でき、効率的にテレメトリデータを探索する事ができます。

ただしこのメリットは各サービスから出力されるテレメトリデータが semantic conventions に則っていないと失われてしまいます。そして困った事に、実態として semantic conventions に則っていない計装ライブラリが世の中には存在します。具体的には opentelemetry-pythonopentelemetry-python-contrib は一部 semantic conventions に則っていません。 より正確には、古い semantic conventions のまま更新されていないため最新の semantic conventions に則っていない、という感じですが。

このようなケースに対処するため、Attribute を semantic conventions に則らせるべく Attributes Processor を用いて、semantic conventions に則ってない attribute を semantic conventions に則った別の attribute として key 名だけ変えてコピーする必要があったりします(しました)。

このように Collector はほぼ必須です。ツベコベ言わず活用しましょう。

まとめ

  • Observability の目指すところ
    • 予期できていない問題が発生した「後」に、コードの変更なく問題の原因究明が可能な事
    • エスパー/直感に頼らず問題解決ができる事
  • Observability を高めるには計装ライブラリ任せではダメ
    • Activity.SetTag() を積極的に叩く事が大事
    • コンテキストは Middleware/Filter で追加しておくと便利
  • OpenTelemetry Collector はほぼ必須

それでは良き Observability ライフを。

References