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

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

* [TelemetryClient と Activity の挙動を理解する (1) - データモデル](https://qiita.com/TsuyoshiUshio@github/items/f02d74a18d0b5a855fde) 

* [Activity User Guide (原典)](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md#overview)

# 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](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md#tags)を提供しています。それは、ロギングのみに使われるコンテキストを示しています。[Baggage](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md#baggage) は、外部の依存先に伝搬するためのコンテキストを表します。

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

`Activity`(root のものは除く) は[Parent](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md#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](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/DiagnosticSourceUsersGuide.md)を参考にしましょう。
* **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](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md#baggage)を避けて、できるだけ小さく保ちましょう。
* **DO NOT** baggage にセンシティブな情報を詰めることはやめましょう。ほかのプロセスのバウンダリに送られてしまうことがあります。
* **DO** すべてのテレメトリイベントで、activity id を書きましょう。ParentId, Tags, そして Baggage は、１回のオペレーションにつき、最低１回はかかれるべきで、Id により見つかるべきです。Tags と Baggage は、アクティビティのライフタイムで変わることがあります。そして、アクティビティがストップするときに書きだされることは的を得ています。Duration は、アクティビティがストップしたときのみログ出力されるべです。
* **CONSIDER** アクティビティの RootId をすべてのテレメトリで書きます。たとえ、Id のプレフィックスでフィルタリングあなたの使っているログインぐバックエンドがサポートしていない、もしくはとても高価な場合もです。

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

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

# Activity の使い方

## Activity の作成

```csharp
Activity activity = new Activity("Http_In");
```

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

```csharp
actvity.SetStartTime()
  .AddTag("Path", request.Path)
  .AddBaggage("FeatureId", experimentalFeatureId);
```
一度アクティビティが作られたら、スタートするときです。そして、リクエストの実行を実施しましょう。

## Starting and Stopping Activity

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

```csharp
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() 時に自動的にアサインされます。

```csharp
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` にアクセスいｓて、現在のアクティビティの情報を取得することができます。[DiagnositcListener convension](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/DiagnosticSourceUsersGuide.md)に従っています。

アプリケーションは、tags と baggage を アクティビティの Start コールバックの時に、`Activity.Current` に足すかもしれません。[Incoming Request Sample](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md#starting-and-stopping-activity)の中にかいています。私たちは、`HttpContext` を `DiagnosticSource` に渡しています。それによって、アプリケーションは、リクエストプロパティにアクセスできて、現在のアクティビティをよりリッチにすることができます。

## Subscribe to DiagnosticSource 

```csharp
   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

```csharp
    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　が、すべてのアクティビティで少なくとも１度ログ出力されるのが必須です。すべてのテレメトリイベントをクエリーと、アグリゲーションをシンプルにするためにログ出力されるかもしれません。Duration は、SetEndTimeが呼ばれた後に有効です。そして、Activity Stop イベントが発生したときに、ログ出力されます。

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

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

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

Activity.Id は、[HTTP Correlation Protocol](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/HttpCorrelationProtocol.md) の`hierarchical Request-Id`を表します。

## Id フォーマット

`|root-id.id1_id2.id3_id4.`

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

`|` で開始されて、[root-id](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md#root-id) が続き`.`が続き、そして、全てのローカルのアクティビティの識別子が、`.` か `_` で区切られます。
[Root-id](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md#root-id)は、すべてのオペレーションを識別し、`Id` は、特定のアクティビティが含まれているオペレーションを識別します。
`|` は、Id が階層構造になっていることを表しており、ロギングシステムには有効な情報です。

* Id は、1024 バイト以下
* Id は、[Base64](https://en.wikipedia.org/wiki/Base64)で構成されていて, `_`, `.`, `_`, `#` キャラクタを含みます。Base64 と、`_` がnode の中で使われて、ほかのキャラクターは、node を分割するのに使われます。Id は常にいづれかのデリミタで終わります。

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

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

* **MUST** システム全体で１つのオペレーションを十分に特定できうるだけの大きさにします。64(or 128) ビットのランダムナンバーか、Guid
* **MUST** [Base64 characters](https://en.wikipedia.org/wiki/Base64)と、`-` のみを含みます。

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#`

# リファレンス

* [Reference](https://github.com/dotnet/corefx/blob/master/src/System.Diagnostics.DiagnosticSource/src/ActivityUserGuide.md#reference)

# まとめ

がっつり翻訳してみましたが、Activity は、コリレーションの内容を、保持、更新してくれて、親子の関連付けなども行ってくれる便利クラスのようです。ただ、これが、自動でテレメトリを送信することはなさげなので、それは、`DiagnosticListener` の役割のようです。Id も自動で振ってくれるようすなので、なかなかいい感じです。ただ、[W3C TraceContext](https://w3c.github.io/trace-context/?fbclid=IwAR3exAmMf2QMok0eMzegZrguQXYZejVNCCBYSknIHldoRKlYtAjiKFCZPiM)には対応してなさげですが、ローカルで試したところ、`    <PackageReference Include="Microsoft.ApplicationInsights.DependencyCollector" Version="2.8.0" />` を足すと[W3CActivityExtensions.cs](https://github.com/Microsoft/ApplicationInsights-dotnet-server/blob/307ed401875661716ba24ad5979336a8381e62e9/Src/Common/W3C/W3CActivityExtensions.cs?fbclid=IwAR0mEGJXsA1Qul1G5NxVoVM4JAw2XOKJtCguvJg6a6TN3f3n0OtSKpyqZUo)が追加されて、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](https://dictionary.cambridge.org/)