System.Diagnostics.Tracing.EventSource class
この記事では、この API のリファレンス ドキュメントへの補足的な解説を提供します。
EventSource クラスは、イベント トレースに使用する特定のイベントを提供するユーザー クラスによって継承されることを目的としています。 EventSource.WriteEventメソッドは、イベントをログに記録するために呼び出されます。
ほとんどのアプリケーションでは、 EventSource の基本的な機能で十分です。 作成されたイベント メタデータをより詳細に制御する場合は、 EventAttribute 属性をメソッドに適用できます。 高度なイベント ソース アプリケーションの場合、派生イベント ソースに送信されるコマンドをインターセプトしてフィルター処理を変更したり、アクション (データ構造のダンプなど) を継承元によって実行したりできます。 イベント ソースは、dotnet-trace
や Event Tracing for Windows (ETW) ベースのツール (PerfView
やLogman
など) などの EventPipe ベースのツールを使用して、EventListenerとアウトプロセスを使用してインプロセスでアクティブ化できます。 また、データ ディスパッチャーをプログラムで制御およびインターセプトすることもできます。 EventListener クラスは、追加の機能を提供します。
規約
EventSource-derived クラスは、次の規則に従う必要があります。
- ユーザー定義クラスは、シングルトン パターンを実装する必要があります。 シングルトン インスタンスは、従来は
Log
という名前です。 拡張機能では、ユーザーはIDisposable.Dispose
を手動で呼び出して、マネージド コードの実行時にランタイムがシングルトン インスタンスをクリーンアップできないようにする必要があります。 - ユーザー定義の派生クラスは、「高度な使用法」セクションで説明されている高度な "ユーティリティ EventSource" 構成を実装しない限り、
sealed
としてマークする必要があります。 - イベントの発生に関連するリソースを集中的に使用する作業を実行する前に、 IsEnabled() を呼び出します。
- 名前付けパターン
<EventName>Start
と<EventName>Stop
を持つ後続のイベント ID で 2 つのイベント メソッドを宣言することで、EventTask オブジェクトを暗黙的に作成できます。 これらのイベント must クラス定義で互いに隣り合って宣言され、<EventName>Start
メソッド must 先に来ます。 - オブジェクト EventSource 下位互換性を維持し、適切にバージョン管理を試みます。 イベントの既定のバージョンは
0
です。 バージョンは、 Version設定することで変更できます。 ペイロードのプロパティを変更するたびに、イベントのバージョンを変更します。 常に新しいペイロード プロパティをイベント宣言の最後に追加します。 これができない場合は、新しい ID を持つ新しいイベントを作成して古いイベントを置き換えます。 - イベント メソッドを宣言するときは、可変サイズのプロパティの前に固定サイズのペイロード プロパティを指定します。
- EventKeywords は、プロバイダーをサブスクライブするときに特定のイベントを指定するためのビット マスクとして使用されます。 キーワードを指定するには、
public const EventKeywords
メンバーを持つpublic static class Keywords
メンバー クラスを定義します。 - EventAttributeを使用して、高価なイベントをEventKeywordsに関連付けます。 このパターンにより、 EventSource のユーザーはこれらの高価な操作をオプトアウトできます。
自己記述 (トレース ログ) 形式とマニフェスト イベント形式の比較
EventSource は、使用されるコンストラクターまたは EventSourceOptionsで設定されるフラグに基づいて、2 つの異なるモードに構成できます。
これまで、これら 2 つの形式は、Event Tracing for Windows (ETW) で使用される 2 つの形式から派生しています。 これら 2 つのモードは、Event Tracing for Windows (ETW) または EventPipe ベースのリスナーを使用する機能には影響しませんが、イベントのメタデータは異なる方法で生成されます。
既定のイベント形式は EtwManifestEventFormatであり、 EventSourceSettingsで指定されていない場合に設定されます。 マニフェスト ベースの EventSource オブジェクトは、初期化時にクラスで定義されたイベントを表す XML ドキュメントを生成します。 そのためには、プロバイダーとイベントのメタデータを生成するために、 EventSource 自体を反映する必要があります。
自己記述 (トレース ログ) イベント形式を使用するには、EventSource(String) コンストラクター、EventSource(String, EventSourceSettings) コンストラクター、またはEventSourceSettingsにEtwSelfDescribingEventFormat
フラグを設定して、EventSourceを構築します。 自己記述型ソースは、初期化時に最小限のプロバイダー メタデータを生成し、 Write(String) が呼び出されたときにのみイベント メタデータを生成します。
実際には、これらのイベント形式の設定は、Windows イベント トレーシング (ETW) に基づく閲覧者の使用にのみ影響します。 ただし、リフレクションとメタデータの生成に必要な時間が原因で、初期化時間とイベントごとの書き込み時間に影響を与える可能性があります。
例
次の例は、 EventSource クラスの簡単な実装を示しています。
using System.Diagnostics.Tracing;
namespace Demo1
{
sealed class MyCompanyEventSource : EventSource
{
public static MyCompanyEventSource Log = new MyCompanyEventSource();
public void Startup() { WriteEvent(1); }
public void OpenFileStart(string fileName) { WriteEvent(2, fileName); }
public void OpenFileStop() { WriteEvent(3); }
}
class Program1
{
static void Main(string[] args)
{
MyCompanyEventSource.Log.Startup();
// ...
MyCompanyEventSource.Log.OpenFileStart("SomeFile");
// ...
MyCompanyEventSource.Log.OpenFileStop();
}
}
}
Imports System.Diagnostics.Tracing
Class MyCompanyEventSource
Inherits EventSource
Public Shared Log As New MyCompanyEventSource()
Public Sub Startup()
WriteEvent(1)
End Sub
Public Sub OpenFileStart(ByVal fileName As String)
WriteEvent(2, fileName)
End Sub
Public Sub OpenFileStop()
WriteEvent(3)
End Sub
End Class
Class Program
Shared Sub Main(ByVal args() As String)
MyCompanyEventSource.Log.Startup()
' ...
MyCompanyEventSource.Log.OpenFileStart("SomeFile")
' ...
MyCompanyEventSource.Log.OpenFileStop()
End Sub
End Class
次の例は、 EventSource クラスのより複雑な実装を示しています。
using System;
using System.Diagnostics.Tracing;
namespace Demo2
{
enum MyColor { Red, Yellow, Blue };
[EventSource(Name = "MyCompany")]
sealed class MyCompanyEventSource : EventSource
{
public static class Keywords
{
public const EventKeywords Page = (EventKeywords)1;
public const EventKeywords DataBase = (EventKeywords)2;
public const EventKeywords Diagnostic = (EventKeywords)4;
public const EventKeywords Perf = (EventKeywords)8;
}
public static class Tasks
{
public const EventTask Page = (EventTask)1;
public const EventTask DBQuery = (EventTask)2;
}
[Event(1, Message = "Application Failure: {0}", Level = EventLevel.Error, Keywords = Keywords.Diagnostic)]
public void Failure(string message) { WriteEvent(1, message); }
[Event(2, Message = "Starting up.", Keywords = Keywords.Perf, Level = EventLevel.Informational)]
public void Startup() { WriteEvent(2); }
[Event(3, Message = "loading page {1} activityID={0}", Opcode = EventOpcode.Start,
Task = Tasks.Page, Keywords = Keywords.Page, Level = EventLevel.Informational)]
public void PageStart(int ID, string url) { if (IsEnabled()) WriteEvent(3, ID, url); }
[Event(4, Opcode = EventOpcode.Stop, Task = Tasks.Page, Keywords = Keywords.Page, Level = EventLevel.Informational)]
public void PageStop(int ID) { if (IsEnabled()) WriteEvent(4, ID); }
[Event(5, Opcode = EventOpcode.Start, Task = Tasks.DBQuery, Keywords = Keywords.DataBase, Level = EventLevel.Informational)]
public void DBQueryStart(string sqlQuery) { WriteEvent(5, sqlQuery); }
[Event(6, Opcode = EventOpcode.Stop, Task = Tasks.DBQuery, Keywords = Keywords.DataBase, Level = EventLevel.Informational)]
public void DBQueryStop() { WriteEvent(6); }
[Event(7, Level = EventLevel.Verbose, Keywords = Keywords.DataBase)]
public void Mark(int ID) { if (IsEnabled()) WriteEvent(7, ID); }
[Event(8)]
public void LogColor(MyColor color) { WriteEvent(8, (int)color); }
public static MyCompanyEventSource Log = new MyCompanyEventSource();
}
class Program
{
static void Main(string[] args)
{
MyCompanyEventSource.Log.Startup();
Console.WriteLine("Starting up");
MyCompanyEventSource.Log.DBQueryStart("Select * from MYTable");
var url = "http://localhost";
for (int i = 0; i < 10; i++)
{
MyCompanyEventSource.Log.PageStart(i, url);
MyCompanyEventSource.Log.Mark(i);
MyCompanyEventSource.Log.PageStop(i);
}
MyCompanyEventSource.Log.DBQueryStop();
MyCompanyEventSource.Log.LogColor(MyColor.Blue);
MyCompanyEventSource.Log.Failure("This is a failure 1");
MyCompanyEventSource.Log.Failure("This is a failure 2");
MyCompanyEventSource.Log.Failure("This is a failure 3");
}
}
}
Imports System.Diagnostics.Tracing
Enum MyColor
Red
Yellow
Blue
End Enum 'MyColor
<EventSource(Name:="MyCompany")>
Class MyCompanyEventSource1
Inherits EventSource
Public Class Keywords
Public Const Page As EventKeywords = CType(1, EventKeywords)
Public Const DataBase As EventKeywords = CType(2, EventKeywords)
Public Const Diagnostic As EventKeywords = CType(4, EventKeywords)
Public Const Perf As EventKeywords = CType(8, EventKeywords)
End Class
Public Class Tasks
Public Const Page As EventTask = CType(1, EventTask)
Public Const DBQuery As EventTask = CType(1, EventTask)
End Class
<[Event](1, Message:="Application Failure: {0}", Level:=EventLevel.Error, Keywords:=Keywords.Diagnostic)>
Public Sub Failure(ByVal message As String)
WriteEvent(1, message)
End Sub
<[Event](2, Message:="Starting up.", Keywords:=Keywords.Perf, Level:=EventLevel.Informational)>
Public Sub Startup()
WriteEvent(2)
End Sub
<[Event](3, Message:="loading page {1} activityID={0}", Opcode:=EventOpcode.Start, Task:=Tasks.Page, Keywords:=Keywords.Page, Level:=EventLevel.Informational)>
Public Sub PageStart(ByVal ID As Integer, ByVal url As String)
If IsEnabled() Then
WriteEvent(3, ID, url)
End If
End Sub
<[Event](4, Opcode:=EventOpcode.Stop, Task:=Tasks.Page, Keywords:=Keywords.Page, Level:=EventLevel.Informational)>
Public Sub PageStop(ByVal ID As Integer)
If IsEnabled() Then
WriteEvent(4, ID)
End If
End Sub
<[Event](5, Opcode:=EventOpcode.Start, Task:=Tasks.DBQuery, Keywords:=Keywords.DataBase, Level:=EventLevel.Informational)>
Public Sub DBQueryStart(ByVal sqlQuery As String)
WriteEvent(5, sqlQuery)
End Sub
<[Event](6, Opcode:=EventOpcode.Stop, Task:=Tasks.DBQuery, Keywords:=Keywords.DataBase, Level:=EventLevel.Informational)>
Public Sub DBQueryStop()
WriteEvent(6)
End Sub
<[Event](7, Level:=EventLevel.Verbose, Keywords:=Keywords.DataBase)>
Public Sub Mark(ByVal ID As Integer)
If IsEnabled() Then
WriteEvent(7, ID)
End If
End Sub
<[Event](8)>
Public Sub LogColor(ByVal color As MyColor)
WriteEvent(8, Fix(color))
End Sub
Public Shared Log As New MyCompanyEventSource1()
End Class
Class Program1
Shared Sub Main(ByVal args() As String)
MyCompanyEventSource1.Log.Startup()
Console.WriteLine("Starting up")
MyCompanyEventSource1.Log.DBQueryStart("Select * from MYTable")
Dim url As String = "http:'localhost"
Dim i As Integer
For i = 0 To 9
MyCompanyEventSource1.Log.PageStart(i, url)
MyCompanyEventSource1.Log.Mark(i)
MyCompanyEventSource1.Log.PageStop(i)
Next i
MyCompanyEventSource1.Log.DBQueryStop()
MyCompanyEventSource1.Log.LogColor(MyColor.Blue)
MyCompanyEventSource1.Log.Failure("This is a failure 1")
MyCompanyEventSource1.Log.Failure("This is a failure 2")
MyCompanyEventSource1.Log.Failure("This is a failure 3")
End Sub
End Class
詳細な使用方法
従来、ユーザー定義の EventSource オブジェクトは、 EventSourceから直接継承することが想定されています。 ただし、高度なシナリオでは、Utility Sources と呼ばれるabstract
EventSource オブジェクトを作成し、インターフェイスを実装できます。 これらの手法の 1 つまたは両方を使用すると、異なる派生ソース間でコードを共有できます。
重要
抽象 EventSource オブジェクトは、キーワード、タスク、オペコード、チャネル、またはイベントを定義できません。
重要
イベント メタデータを生成するときに実行時に名前の競合を回避するために、 EventSourceでインターフェイスを使用する場合は、インターフェイス メソッドを明示的に実装しないでください。
次の例は、インターフェイスを使用する EventSource の実装を示しています。
public interface IMyLogging
{
void Error(int errorCode, string message);
void Warning(string message);
}
public sealed class MySource : EventSource, IMyLogging
{
public static MySource Log = new();
[Event(1)]
public void Error(int errorCode, string message) => WriteEvent(1, errorCode, message);
[Event(2)]
public void Warning(string message) => WriteEvent(2, message);
}
次の例は、Utility EventSource パターンを使用する EventSource の実装を示しています。
public abstract class UtilBaseEventSource : EventSource
{
protected UtilBaseEventSource()
: base()
{ }
protected UtilBaseEventSource(bool throwOnEventWriteErrors)
: base(throwOnEventWriteErrors)
{ }
// helper overload of WriteEvent for optimizing writing an event containing
// payload properties that don't align with a provided overload. This prevents
// EventSource from using the object[] overload which is expensive.
protected unsafe void WriteEvent(int eventId, int arg1, short arg2, long arg3)
{
if (IsEnabled())
{
EventSource.EventData* descrs = stackalloc EventSource.EventData[3];
descrs[0] = new EventData { DataPointer = (IntPtr)(&arg1), Size = 4 };
descrs[1] = new EventData { DataPointer = (IntPtr)(&arg2), Size = 2 };
descrs[2] = new EventData { DataPointer = (IntPtr)(&arg3), Size = 8 };
WriteEventCore(eventId, 3, descrs);
}
}
}
public sealed class OptimizedEventSource : UtilBaseEventSource
{
public static OptimizedEventSource Log = new();
public static class Keywords
{
public const EventKeywords Kwd1 = (EventKeywords)1;
}
[Event(1, Keywords = Keywords.Kwd1, Level = EventLevel.Informational, Message = "LogElements called {0}/{1}/{2}.")]
public void LogElements(int n, short sh, long l) => WriteEvent(1, n, sh, l); // uses the overload we added!
}
次の例は、ライブラリ内のコンポーネントに関する情報をトレースするための EventSource の実装を示しています。
public class ComplexComponent : IDisposable
{
internal static Dictionary<string, string> _internalState = new();
private string _name;
public ComplexComponent(string name)
{
_name = name ?? throw new ArgumentNullException(nameof(name));
ComplexSource.Log.NewComponent(_name);
}
public void SetState(string key, string value)
{
lock (_internalState)
{
_internalState[key] = value;
ComplexSource.Log.SetState(_name, key, value);
}
}
private void ExpensiveWork1() => System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(250));
private void ExpensiveWork2() => System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(250));
private void ExpensiveWork3() => System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(250));
private void ExpensiveWork4() => System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(250));
public void DoWork()
{
ComplexSource.Log.ExpensiveWorkStart(_name);
ExpensiveWork1();
ExpensiveWork2();
ExpensiveWork3();
ExpensiveWork4();
ComplexSource.Log.ExpensiveWorkStop(_name);
}
public void Dispose()
{
ComplexSource.Log.ComponentDisposed(_name);
}
}
internal sealed class ComplexSource : EventSource
{
public static ComplexSource Log = new();
public static class Keywords
{
public const EventKeywords ComponentLifespan = (EventKeywords)1;
public const EventKeywords StateChanges = (EventKeywords)(1 << 1);
public const EventKeywords Performance = (EventKeywords)(1 << 2);
public const EventKeywords DumpState = (EventKeywords)(1 << 3);
// a utility keyword for a common combination of keywords users might enable
public const EventKeywords StateTracking = ComponentLifespan & StateChanges & DumpState;
}
protected override void OnEventCommand(EventCommandEventArgs args)
{
base.OnEventCommand(args);
if (args.Command == EventCommand.Enable)
{
DumpComponentState();
}
}
[Event(1, Keywords = Keywords.ComponentLifespan, Message = "New component with name '{0}'.")]
public void NewComponent(string name) => WriteEvent(1, name);
[Event(2, Keywords = Keywords.ComponentLifespan, Message = "Component with name '{0}' disposed.")]
public void ComponentDisposed(string name) => WriteEvent(2, name);
[Event(3, Keywords = Keywords.StateChanges)]
public void SetState(string name, string key, string value) => WriteEvent(3, name, key, value);
[Event(4, Keywords = Keywords.Performance)]
public void ExpensiveWorkStart(string name) => WriteEvent(4, name);
[Event(5, Keywords = Keywords.Performance)]
public void ExpensiveWorkStop(string name) => WriteEvent(5, name);
[Event(6, Keywords = Keywords.DumpState)]
public void ComponentState(string key, string value) => WriteEvent(6, key, value);
[NonEvent]
public void DumpComponentState()
{
if (IsEnabled(EventLevel.Informational, Keywords.DumpState))
{
lock (ComplexComponent._internalState)
{
foreach (var (key, value) in ComplexComponent._internalState)
ComponentState(key, value);
}
}
}
}
.NET