前回の記事では、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]
Activity
は、Tagsを提供しています。それは、ロギングのみに使われるコンテキストを示しています。Baggage は、外部の依存先に伝搬するためのコンテキストを表します。
すべての Activity
は Id
をもっています。特定のアプリケーションのリクエストを定義しており、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の中にかいています。私たちは、HttpContext
を DiagnosticSource
に渡しています。それによって、アプリケーションは、リクエストプロパティにアクセスできて、現在のアクティビティをよりリッチにすることができます。
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 Protocol のhierarchical 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:
*