LoginSignup
4
1

More than 5 years have passed since last update.

TelemetryClient と Activity の挙動を理解する (2) - Activity

Posted at

前回の記事では、Application Insights のデータモデルについて説明した。第二回は、Activity について整理してみたい。

Activity User Guide

このドキュメントは Actvitiy について説明します。Actvitiy は、診断コンテキスト diagnostic context をストアしたり、アクセスしたりできるようにするクラスです。ロギングによって、使われます。

このドキュメントは、Activity のアーキテクチャオーバービューと使い方について説明します。

Overview

アプリケーションが、オペレーションを実行するとき、例えば HTTPリクエストや、Queue からのタスクなど、アプリケーションは、Activity を生成します。Activity は、リクエストの実行が、システム間でトラックできるようにします。Activity に保存されているContext の例としては、HTTPのリクエストパスだったり、メソッドだったり、user-agent だったり、correlation id だったりします。すべての重要な詳細は、すべてのトレースと共にログされます。詳細は[Activity Reference]

(https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md#activity-reference)にあります。

Activity は、Tagsを提供しています。それは、ロギングのみに使われるコンテキストを示しています。Baggage は、外部の依存先に伝搬するためのコンテキストを表します。

すべての ActivityId をもっています。特定のアプリケーションのリクエストを定義しており、Activity が開始されたときに生成されます。

Activity(root のものは除く) はParentを持っています。in-process もしくは、外部のものです。例えば、アプリケーションがリクエストを受け取って、外部依存サービスを呼び出した場合、外部サービスを呼び出すための Activity が存在し、それは、親の Activity すなわち、リクエストの受け取りを表す Parent Activity を持ちます。外部依存呼び出しの Activity Id は、リクエストと共に送信されて、おそらく、外部依存サービスのActivityの、ParentId として使われます。これにより、子と親の間のユニークなマッピングが可能になります。Parent は、Activity がスタートしたらアサインされます。

Activities は、プラットフォームやフレームワークのコードの中で、おそらく、作られたり、開始/終了したりします。アプリケーションは、アクティビティのイベントを通知する必要があり、実行された Current Activity にアクセスする必要があります。ですので、Activity を作ったコードは、該当イベントを、DiagnosticSource に書いてあげる必要があります。:

  • DO - 特定の Activity type に対して、フィルタリングを行うために、新しい DiagnosticListenerを 作成します。例えば、Incoming と Outgoing の Http Requests は、別の違う DiagnosticListener であるべきです。DignosticSource User Guideを参考にしましょう。
  • DO - Activity の生成と、コールのスタートを、DignosticSource.IsEnabled を呼ぶことでガードしましょう。これにより、だれもリスニングしていない Activities 作成することを防ぐことができます。イベント名ベースのフィルタリングやサンプリングを可能にします。
  • DO - Activity のメソッドが、Activity のイベントが常に DiagnosticSource に書かれることを保証する代わりに、DiagnosticSource.StartActivity(Activity)DiagnosticSource.StopActivity(Activity) メソッドをつかいましょう。
  • DO 必要な全てのコンテキストをDiagnosticsListnerに引き渡しましょう。そうすればあなたのアプリケーションは、Activityがよりよくなるでしょう。例えば、HTTPリクエストを受け取ったとき、アプリケーションは、カスタムタグを追加するために、HttpContext のインスタンスが必要です。(method, path, user-agent, など)
  • CONSIDER Baggageを避けて、できるだけ小さく保ちましょう。
  • DO NOT baggage にセンシティブな情報を詰めることはやめましょう。ほかのプロセスのバウンダリに送られてしまうことがあります。
  • DO すべてのテレメトリイベントで、activity id を書きましょう。ParentId, Tags, そして Baggage は、1回のオペレーションにつき、最低1回はかかれるべきで、Id により見つかるべきです。Tags と Baggage は、アクティビティのライフタイムで変わることがあります。そして、アクティビティがストップするときに書きだされることは的を得ています。Duration は、アクティビティがストップしたときのみログ出力されるべです。
  • CONSIDER アクティビティの RootId をすべてのテレメトリで書きます。たとえ、Id のプレフィックスでフィルタリングあなたの使っているログインぐバックエンドがサポートしていない、もしくはとても高価な場合もです。

Current Activity は、スタティックの変数で公開されています。Activity.Current そして、すべてのcall context を流れます。async 呼び出しも含みます。ですので、すべての、Start/Stop イベントのコールバックで利用可能です。

アプリケーションは、Activity.Current にコードのどこからでもアクセスする可能性があり、Activity の Context に保存されているコンテキストとともに、イベントのログ出力をどこからでも行うかもしれません。

Activity の使い方

Activity の作成

Activity activity = new Activity("Http_In");

アクティビティは、オペレーション名と一緒に作られる必要があります。これは、大きめの名前で、グルーピングやログのフィルタに使われます。アクティビティが作られた後、詳細を追加することができます。Start time, Tags, そして Baggage

actvity.SetStartTime()
  .AddTag("Path", request.Path)
  .AddBaggage("FeatureId", experimentalFeatureId);

一度アクティビティが作られたら、スタートするときです。そして、リクエストの実行を実施しましょう。

Starting and Stopping Activity

Start()Stop() メソッドは、Activity.Current を維持します。これは、async の呼び出しや、リクエストの処理の間有効です。もし、activity が開始されたら、Activity.Current は Id と Parent を持ちます。

public void OnIncomingRequest(DiagnosticListener httpListener, HttpContext context) 
{
  if (httpListener.IsEnabled("Http_In"))
  {
     Activity activity = new Activity("Http_In");
     activity.SetParentId(context.Request.headers["Request-id"])
     foreach (var pair in context.Request.Headers["Correlation-Context"])
     {
         var baggageItem = NameValueHeaderValue.Parse(pair);
         activity.AddBaggage(baggageItem.Key, baggageItem.Value);
      }
      httpListener.StartActivity(activity, new {context});
      try {
         // process request ...
      } finally {
         httpListener.StopActivity(activity, new {context});
      }
   }
}

NOTE
* Activity.Start()やStop()メソッドの代わりに、上のサンプルでは、DiagnosticSource.StartActivity()StopActivity() メソッドを呼んでいます。それは、DiagnosticSource にイベントを書き込みます。
* Activity の作成は、DiagnosticSource.IsEnabled の呼び出しでガードされます。もし、だれも、DiagnosticSource をリスニングしていない時のパフォーマンスインパクトを削減できます。

Create child Activities

アプリケーションが、外部呼出しをするとき、例えば、外部の Web サービスを呼び出すとき、新しい Activity が作成されるべきです。この子の Activity が既存のアクティビティの一部である場合、Parent が Start() 時に自動的にアサインされます。

public void OnOutgoingRequest(DiagnosticListener httpListener, HttpRequestMessage request)
{
  if (httpListener.IsEnabled() && httpListener.IsEnabled("Http_Out", request))
  {
    var activity = new Activity("Http_Out");
    httpListener.StartActivity(activity, new {request})

    request.Headers.Add("Request-Id", activity.Id);
    request.Headers.Add("Correlation-Context", baggageToHeader(activity.Baggage));
    try {
          // process request
    } finally {
         // stop activity
         httpListener.StopActivity(activity, new {request});
    }
  }
}

子の Activity は、自動的に Baggage を親から引き継ぎます。上記のサンプルは、どのように、baggage が下流のWebサービスに HTTP request ヘッダを使って伝搬されうるかということも示しています。
前のサンプルにあるとおり、Activity の生成は、DiagnosticSource.IsEnabled() でガードされるべきです。このケースでは、ツールベースのリクエストプロパティを防ぐことができます。例えば URI などです。他の DiagnosticSource がincoming と outgoing のHTTP アクティビティで違うものが使われているのに注意してください。このことは、あなたに、イベントのフィルターで分離できることを意味します。

Activity Event のリスニング

アプリケーションは、Activity Event をリッスンして、ログを出力しているかもしれません。アプリケーションは、Activity.Current にアクセスいsて、現在のアクティビティの情報を取得することができます。DiagnositcListener convensionに従っています。

アプリケーションは、tags と baggage を アクティビティの Start コールバックの時に、Activity.Current に足すかもしれません。Incoming Request Sampleの中にかいています。私たちは、HttpContextDiagnosticSource に渡しています。それによって、アプリケーションは、リクエストプロパティにアクセスできて、現在のアクティビティをよりリッチにすることができます。

Subscribe to DiagnosticSource

   DiagnosticListener.AllListeners.Subscribe(delegate (DiagnosticListener listener)
    {
        if (listener.Name == "MyActivitySource")
        {
            listener.Subscribe(delegate (KeyValuePair<string, object> value)
            {
                if (value.Key.EndsWith("Start", StringComparison.Ordinal))
                    LogActivityStart();
                else if (value.Key.EndsWith("Stop", StringComparison.Ordinal))
                    LogActivityStop();
            });
        }
    }

Log Events

    public void LogActivityStart()
    {
        var document = new Dictionary<string,object>
        {
            ["Message"] = $"Activity {activity.OperationName} was started",
            ["LogLevel"] = LogLevel.Info,
            ["Id"] = activity.Id,
            ["ParentId"] = activity.ParentId,
            ["StartTime"] = activity.StartTimeUtc,
        }
        //log tags and baggage if needed
        ...// send document to log storage       
    }

    public void LogActivityStop()
    {
        var document = new Dictionary<string,object>
        {
            ["Message"] = $"Activity {activity.OperationName} is being stopped",
            ["LogLevel"] = LogLevel.Info,
            ["Id"] = activity.Id,
            ["ParentId"] = activity.ParentId,
            ["Duration"] = activity.Duration
        };

        //warning: Baggage or Tag could have duplicated keys!
        foreach (var kv in activity.Tags)
            document[kv.Key] = kv.Value;
        foreach (var kv in activity.Baggage)
            document[kv.Key] = kv.Value;
        ...// send document to log storage
    }

    public void Log(LogLevel level, string message)
    {
        var document = new Dictionary<string,object>
        {
            ["Message"] = message,
            ["LogLevel"] = logLevel,
        };

        if (Activity.Current != null)
        {
            document["Id"] = activity.Id;
            //add tags, baggage and ParentId if needed
        }
        ...// send document to log storage
    }

Activity Id がすべてのイベントで、ログ出力されていることが必須。ParentId, Tags, Baggage が、すべてのアクティビティで少なくとも1度ログ出力されるのが必須です。すべてのテレメトリイベントをクエリーと、アグリゲーションをシンプルにするためにログ出力されるかもしれません。Duration は、SetEndTimeが呼ばれた後に有効です。そして、Activity Stop イベントが発生したときに、ログ出力されます。

NOTE: アクティビティは、Tags や Baggage には、重複するキーを認めています。

Activity Id

Activity の主なゴールは、テレメトリイベントをユーザリクエストを紐づけることです。Activity.Id は、この機能のキーパーとです。

アプリケーションは、Activity を開始して、終えるべき仕事の論理的なピースを表します。一つの Activity は、ほかの Activity の子としてスタートされることがあります。全体のオペレーションは、Activity のツリーとして表現されるかもしれません。すべてのオペレーションは、分散システムで、実施されて、Activity ツリーの森として表現されるかもしれません。Id は、Activity を森の中でユニークに識別します。それは、階層的な構造をもっており、効果的に Activity のツリーを表現します。

Activity.Id は、HTTP Correlation Protocolhierarchical Request-Idを表します。

Id フォーマット

|root-id.id1_id2.id3_id4.

たとえば
|a000b421-5d183ab6.1.8e2d4c28_1.

| で開始されて、root-id が続き.が続き、そして、全てのローカルのアクティビティの識別子が、._ で区切られます。
Root-idは、すべてのオペレーションを識別し、Id は、特定のアクティビティが含まれているオペレーションを識別します。
| は、Id が階層構造になっていることを表しており、ロギングシステムには有効な情報です。

  • Id は、1024 バイト以下
  • Id は、Base64で構成されていて, _, ., _, # キャラクタを含みます。Base64 と、_ がnode の中で使われて、ほかのキャラクターは、node を分割するのに使われます。Id は常にいづれかのデリミタで終わります。

Root Id

アクティビティを最初にスタートさせたとき、あなたは、Activity.SetParentId(string) で、ルートIdをオプションでセット可能です。もし、セットしなければ、アクティビティは、root-id を生成します。例えば a000b421-5d183ab6

もし、他のプロセスからの、ParentId がなくて、生成したいなら、Root-Id に関して次のようなことを心にとめておいてください。

  • MUST システム全体で1つのオペレーションを十分に特定できうるだけの大きさにします。64(or 128) ビットのランダムナンバーか、Guid
  • MUST Base64 charactersと、- のみを含みます。

root id を取得するには、Activity.RootId を使いましょう。ただし、ParentId がわたされるか、Activity が開始された後で。

Child Activity and Parent Id

Internal Parent

子アクティビティは、親と同じプロセスでスタートします。Parent.Id を取得して、自分の Id を適切な Parent.Id にInt型のサフィックスをつけて作成します。例えば <Parent.Id>.1. サフィックスは、Int型で、同じ親からスタートした子アクティビティの番号です。

アクティビティは、Id を生成して、parent-id.local-id のフォーマットに従います。

External Parent

親が別プロセスのアクティビティの場合、Parent-Id をStart する前に、アサインする必要があります。Activity.SetParentId(string) を使います。Activity は、ほかのサフィックスを Id に使うかもしれません。Root Id で説明したとおり、_ のデリミタが、親が、ほかのプロセスから来たことを表します。

もし、外部の ParentId が、| からスタートしていないなら、アクティビティは、| と自分のId を先頭につけて、ParentIdをキープします。同じように、最後が . で終わっていないなら、追加します。

アクティビティは、Id を生成して、parent-id.local-id_ というフォーマットに従います。

Id オーバーフロー

local-id を Parent.Id に足すことは、長さの制限を超えてしまう可能性を意味します。オーバーフローした場合、Parent.Id の最後のバイトは、削除されて、32-bit のランダムな、小文字の16進数でエンコードされた整数型と#デリミタで埋められます。これは、オーバーフローを表します。<Beginning-Of-Parent-Id>.local-id#

リファレンス

まとめ

がっつり翻訳してみましたが、Activity は、コリレーションの内容を、保持、更新してくれて、親子の関連付けなども行ってくれる便利クラスのようです。ただ、これが、自動でテレメトリを送信することはなさげなので、それは、DiagnosticListener の役割のようです。Id も自動で振ってくれるようすなので、なかなかいい感じです。ただ、W3C TraceContextには対応してなさげですが、ローカルで試したところ、<PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.8.0" /> を足すとW3CActivityExtensions.csが追加されて、W3C の Traceparent や Tracestate が使えるようになるようです。次は、TelemetryClient と Activity の関係を見ていきたいと思います。

Dictionary

  • causality noun[u]: the principle that there is a cause for everything that happens
  • consume verb[t]: to eat or drink, especially a lot of something:
  • trace noun [c or u]: a sign that something has happened or existed:
  • log noun [c]: a full written record of a journey, a period of time, or an event:
  • baggage noun [u]: the beliefs and feelings that influence how you think and behave - We all carry a lot of emotional baggage around with us.
  • enrich verb [t]: to improve the quality of something by adding something else:
  • coarse adj: rough and not smooth or soft, or not in very small pieces:
  • instrumentation noun [u]: the set of instruments that are used to operate a machine
  • prepend verb [t]: ​ to add something to the beginning of something else, especially a piece of data (= information) to the beginning of a computer instruction:
  • intact adj: complete and in the original state:
    *

  • Cambridge dictionary

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1