👌

ちょっとだけ Inside Application Insights SDK (.NET)

に公開

ちょっとだけ Inside Application Insights SDK (.NET)

Application Insights の使い方じゃなくて、Application Insights SDK (.NET) がどうやってテレメトリを送信しているかです。

概要

大雑把にこんな感じです。

[Telemetry Client] --> [Telemetry Initializers] --> [Telemetry Processors] --> [Telemetry Channel] --> [Application Insights サーバー]

Telemetry Client を使って直接テレメトリを送信することもできますが、明示的に書かなくてもある程度は何とかなります。

Microsoft.Extensions.Logging

.NET 公式のロギングフレームワークは次のようにすることで、ログをトレーステレメトリとして Application Insights に送信します。

実装は ApplicationInsightsLogger にあります。一部抜粋しますが、受け取ったログを Telemetry Client を用いて送信しているのが分かります。

                        this.telemetryClient.TrackTrace(traceTelemetry);

Telemetry Collector

.NET で HTTP リクエストを送信すると、Application Insights のポータルで、リクエスト URL やレスポンスコードを確認することができます。それを担っているのが HttpCoreDiagnosticSourceListener という Telemetry Collectorです。(多分)

パフォーマンスカウンターや ASP.NET Core にリクエスト処理などのテレメトリもそれぞれ Telemetry Collector があります。

Telemetry Collector は情報を集めた後、Telemetry Client を用いてテレメトリを送信します。

テレメトリの送信

Telemetry Client に送信したテレメトリは直接 Application Insights に送信されません。前述したように間にいくつかのインスタンスで処理されます。だいたいこんな感じです。

+------------------+
|                  | --> [Telemetry Initializer A]
|                  | --> [Telemetry Initializer B]
|                  | --> [Telemetry Initializer C]
| Telemetry Client |
|                  | --> [Telemetry Processor A]
|                  |        +--> [Telemetory Processor B]
|                  |                +--> [TransmissionProcessor]
|                  |                        +--> [Telemetry Channel]
|                  |                                +--> [Application Insights Server]
+------------------+

Telemetry Initializer

Telemetry Client は、まずは登録されているすべての Telemetry Initializer に対してテレメトリを渡します。主にテレメトリの加工に使います。しょっちゅう使うので、最初に覚えておくといいでしょう。

登録された Telemetry Initializer が順次呼び出され、テレメトリを加工します。

public void ConfigureServices(IServiceCollection services)
{
    // services.AddSingleton<ITelemetryInitializer, CustomTelemetryInitializer>();
    // 何度も同じのが登録されるのを防ぐ書き方
    services.TryAddEnumerable(
        ServiceDescriptor.Singleton<ITelemetryInitializer, CustomTelemetryInitializer>()
    );
}
class CustomTelemetryInitializer : ITelemetryInitializer
{
    public void Initialize(ITelemetry item)
    {
        item.Context.User.AuthenticatedUserId = "taro";
    }
}

標準で様々な Telemetry Initializer が登録されています。

Telemetry Processor

すべての Telemetry Initializer を通して加工が済んだテレメトリは、Telemetry Client によって Telemetory Processor に渡されます。

最終的に TransmissionProcessor が呼び出されます。このプロセッサーが Telemetry Channel にテレメトリを送信しています。

        public void Process(ITelemetry item)
        {
            TelemetryDebugWriter.WriteTelemetry(item);

            this.sink.TelemetryChannel.Send(item);
        }

Telemetry Processor は Decorator Pattern になっていて、次のコード例のように値を加工することができます。(テレメトリの加工自体は Telemetry Initializer で行った方がいいですが)

public void ConfigureServices(IServiceCollection services)
{
        services.AddApplicationInsightsTelemetryProcessor<CustomTelemetryProcessor>();
}
class CustomTelemetryProcessor(ITelemetryProcessor next) : ITelemetryProcessor
{
    public void Process(ITelemetry item)
    {
        item.Context.User.AuthenticatedUserId = "taro";

        next.Process(item);
    }
}

Telemetry Processor の本当の目的はテレメトリのフィルタリングです。次の Telemetry Processor を呼び出さなければ、そこで処理は終了し、テレメトリが送信されることはありません。

Application Insights はデータを間引いてサンプリングをします。公式で用意されている SamplingTelemetryProcessor はサンプリングをする Telemetry Processor の実装です。

一部抜粋しますが、サンプリングされたデータを次の Telemetry Processor に回すような処理が書かれています。(実際はもっと複雑ですし、サンプリングを行うほかの実装もあるようです)

            if (isSampledIn)
            {
                if (advancedSamplingSupportingTelemetry != null)
                {
                    this.HandlePossibleProactiveSampling(item, samplingPercentage, advancedSamplingSupportingTelemetry);
                }
                else
                {
                    this.SampledNext.Process(item);
                }
            }

なお、Telemetry Initializer は機構上非同期処理ができません。非同期処理をする場合は Telemetry Processor を利用します。

void Process(ITelemetry item)
{
    Task.Run(() => ProcessAsync(item));
}

async Task ProcessAsync(ITelemetry item)
{
    var user = await GetUserAsync();
    item.Context.User.AuthenticatedUserId = user.Id;
    next.Process(item);
}

Telemetry Channel

Telemetry Channel はテレメトリを貯めこんでおいて、あとでまとめて Application Insights サーバーに送信するのが目的です。通常は ServerTelemetryChannel が使われるようです。

Telemetry Channel を独自実装に切り替えることもできます。ITelemetryChannel インターフェイスを実装したカスタムクラスを登録するだけです。

Application Insights には送信しないテスト用の Telemetry Channel や、Application Insights でない類似サービスにテレメトリを送るなんてこともできます。

最後に

Application Insights SDK (.NET) の大体の仕組みはこんな感じです。いろんな要求に耐えられるように、ぱっとはわかりにくい仕組みになっています。特に、テレメトリを生成したのがどの Telemetry Collector なのかを調べるのは大変です。

カスタムの Telemetry Initializer を作成し登録し、Initialize() メソッドにブレークポイントを仕掛けてデバッガーで止め、スタックトレースを追いかけていけば、テレメトリを生成している Telemetry Collector が分かります。

こういう拡張性の高い仕組みは初見は難しく感じますが、わかってしまえばそうでもありません。むしろ、いろんな拡張ができるのでありがたいです。

Discussion