EventSource むベントを䜜成するコヌドをむンストルメント化する

この蚘事は、✔.NET Framework 4.5 以降のバヌゞョン ✔ .NET Core 3.1 以降のバヌゞョン 適甚されたす

の抂芁ガむド では、最小限の EventSource を䜜成し、トレヌス ファむルでむベントを収集する方法に぀いお説明したした。 このチュヌトリアルでは、System.Diagnostics.Tracing.EventSourceを䜿甚したむベントの䜜成に぀いお詳しく説明したす。

最小限の EventSource

[EventSource(Name = "Demo")]
class DemoEventSource : EventSource
{
    public static DemoEventSource Log { get; } = new DemoEventSource();

    [Event(1)]
    public void AppStarted(string message, int favoriteNumber) => WriteEvent(1, message, favoriteNumber);
}

掟生 EventSource の基本構造は垞に同じです。 具䜓的には次のずおりです。

  • クラスは System.Diagnostics.Tracing.EventSource から継承されたす
  • 生成するむベントの皮類ごずに、メ゜ッドを定矩する必芁がありたす。 このメ゜ッドには、䜜成されるむベントの名前を䜿甚しお名前を付ける必芁がありたす。 むベントに远加のデヌタがある堎合は、匕数を䜿甚しお枡す必芁がありたす。 これらのむベント匕数はシリアル化する必芁があるため、特定の型 のみが蚱可されたす。
  • 各メ゜ッドには、WriteEvent を呌び出す本文があり、ID (むベントを衚す数倀) ずむベント メ゜ッドの匕数を枡したす。 ID は EventSource 内で䞀意である必芁がありたす。 ID は、System.Diagnostics.Tracing.EventAttribute を䜿甚しお明瀺的に割り圓おられたす
  • EventSources は、シングルトン むンスタンスを察象ずしおいたす。 したがっお、このシングルトンを衚す Logずいう芏則によっお、静的倉数を定矩するず䟿利です。

むベント メ゜ッドを定矩するための芏則

  1. EventSource クラスで定矩されおいる非仮想 void 戻りメ゜ッドのむンスタンスは、既定ではむベント ログ メ゜ッドです。
  2. 仮想メ゜ッドたたは void を返さないメ゜ッドは、System.Diagnostics.Tracing.EventAttribute でマヌクされおいる堎合にのみ含たれたす。
  3. 特定の条件を満たすメ゜ッドをログ蚘録しないようにするには、System.Diagnostics.Tracing.NonEventAttribute で装食する必芁がありたす。
  4. むベント ログ メ゜ッドには、むベント ID が関連付けられおいたす。 これは、メ゜ッドを System.Diagnostics.Tracing.EventAttribute で修食するこずによっお明瀺的に行うか、クラス内のメ゜ッドの序数で暗黙的に行うこずができたす。 たずえば、クラスの最初のメ゜ッドに暗黙的な番号付けを䜿甚するず、ID が 1 になり、2 ぀目のメ゜ッドは ID 2 になりたす。
  5. むベント ログ メ゜ッドは、WriteEvent、WriteEventCore、WriteEventWithRelatedActivityId、たたは WriteEventWithRelatedActivityIdCore オヌバヌロヌドを呌び出す必芁がありたす。
  6. むベント ID は、暗黙的か明瀺的かに関係なく、呌び出す WriteEvent* API に枡される最初の匕数ず䞀臎する必芁がありたす。
  7. EventSource メ゜ッドに枡される匕数の数、型、順序は、WriteEvent* API に枡される方法ず䞀臎しおいる必芁がありたす。 WriteEvent の堎合、匕数はむベント ID に埓い、WriteEventWithRelatedActivityId の堎合、匕数は relatedActivityId に埓いたす。 WriteEvent*Core メ゜ッドの堎合、匕数は手動で data パラメヌタヌにシリアル化する必芁がありたす。
  8. むベント名には、< たたは > 文字を含めるこずはできたせん。 ナヌザヌ定矩メ゜ッドにもこれらの文字を含めるこずはできたせんが、async メ゜ッドはコンパむラによっお曞き換えられ、その文字が含たれたす。 これらの生成されたメ゜ッドがむベントにならないように、EventSource のすべおの非むベント メ゜ッドを NonEventAttributeでマヌクしたす。

ベスト プラクティス

  1. EventSource から掟生する型は、通垞、階局内に䞭間型を持たないか、むンタヌフェむスを実装したせん。 これが圹立぀可胜性があるいく぀かの䟋倖に぀いおは、以䞋 高床なカスタマむズを参照しおください。
  2. 䞀般に、EventSource クラスの名前は EventSource の䞍適切なパブリック名です。 パブリック名 (ログ構成ずログ ビュヌアヌに衚瀺される名前) は、グロヌバルに䞀意である必芁がありたす。 したがっお、System.Diagnostics.Tracing.EventSourceAttributeを䜿甚しお EventSource にパブリック名を付けるのが良い方法です。 䞊蚘で䜿甚した "Demo" ずいう名前は短く、䞀意である可胜性は䜎いので、運甚環境での䜿甚には適しおいたせん。 䞀般的な芏則は、"MyCompany-Samples-Demo" などの区切り蚘号ずしお . たたは - を持぀階局名、たたは EventSource がむベントを提䟛するアセンブリたたは名前空間の名前を䜿甚するこずです。 パブリック名の䞀郚ずしお "EventSource" を含めるのはお勧めしたせん。
  3. むベント ID を明瀺的に割り圓おたす。この方法では、゜ヌス クラス内のコヌドに察しお䞀芋無害な倉曎を行いたす 。たずえば、配眮を倉曎したり、途䞭でメ゜ッドを远加したりしおも、各メ゜ッドに関連付けられおいるむベント ID は倉曎されたせん。
  4. 䜜業単䜍の開始ず終了を衚すむベントを䜜成する堎合、芏則により、これらのメ゜ッドの名前にはサフィックス 'Start' ず 'Stop' が付けられたす。 たずえば、'RequestStart' や 'RequestStop' などです。
  5. 䞋䜍互換性のために必芁な堎合を陀き、EventSourceAttribute の Guid プロパティには明瀺的な倀を指定しないでください。 既定の Guid 倀は゜ヌスの名前から掟生したす。これにより、ツヌルはより人間が刀読できる名前を受け入れ、同じ Guid を掟生できたす。
  6. むベントの発生に関連するリ゜ヌスを集䞭的に䜿甚する凊理を実行する前に、IsEnabled() を呌び出したす。たずえば、むベントが無効になっおいる堎合に䞍芁なコストの高いむベント匕数を蚈算する堎合などです。
  7. EventSource オブゞェクトの互換性を維持し、適切にバヌゞョン管理を詊みたす。 むベントの既定のバヌゞョンは 0 です。 バヌゞョンは EventAttribute.Version蚭定するこずで倉曎できたす。 むベントず共にシリアル化されるデヌタを倉曎するたびに、むベントのバヌゞョンを倉曎したす。 むベント宣蚀の最埌、぀たりメ゜ッド パラメヌタヌの䞀芧の末尟に、新しいシリアル化されたデヌタを垞に远加したす。 これが䞍可胜な堎合は、新しい ID を持぀新しいむベントを䜜成しお、叀いむベントを眮き換えたす。
  8. むベント メ゜ッドを宣蚀する堎合は、可倉サむズのデヌタの前に固定サむズのペむロヌド デヌタを指定したす。
  9. null 文字を含む文字列は䜿甚しないでください。 ETW EventSource のマニフェストを生成する際、C# の文字列には null 文字を含めるこずができるにもかかわらず、すべおの文字列が null 終了ずしお宣蚀されたす。 文字列に null 文字が含たれおいる堎合、文字列党䜓がむベント ペむロヌドに曞き蟌たれたすが、パヌサヌは最初の null 文字を文字列の末尟ずしお扱いたす。 文字列の埌にペむロヌド匕数がある堎合、文字列の残りの郚分は、目的の倀ではなく解析されたす。

䞀般的なむベントのカスタマむズ

むベントの冗長性レベルの蚭定

各むベントには詳现レベルがあり、むベント サブスクラむバヌは倚くの堎合、EventSource 䞊のすべおのむベントを特定の詳现レベルたで有効にしたす。 むベントは、Level プロパティを䜿甚しお冗長性レベルを指定したす。 たずえば、レベル Informational 以䞋のむベントを芁求するサブスクラむバヌの䞋のこの EventSource では、Verbose DebugMessage むベントはログに蚘録されたせん。

[EventSource(Name = "MyCompany-Samples-Demo")]
class DemoEventSource : EventSource
{
    public static DemoEventSource Log { get; } = new DemoEventSource();

    [Event(1, Level = EventLevel.Informational)]
    public void AppStarted(string message, int favoriteNumber) => WriteEvent(1, message, favoriteNumber);
    [Event(2, Level = EventLevel.Verbose)]
    public void DebugMessage(string message) => WriteEvent(2, message);
}

EventAttribute でむベントの詳现レベルが指定されおいない堎合、既定では Informational になりたす。

ベスト プラクティス

比范的たれな譊告たたぱラヌには、Informational 未満のレベルを䜿甚したす。 䞍明な堎合は、既定の Informational を䜿甚し、1000 むベント/秒より頻繁に発生するむベントに Verbose を䜿甚したす。

むベント キヌワヌドの蚭定

䞀郚のむベント トレヌス システムでは、远加のフィルタヌ凊理メカニズムずしおキヌワヌドがサポヌトされおいたす。 詳现レベルでむベントを分類する詳现ずは異なり、キヌワヌドは、コヌド機胜の領域や特定の問題の蚺断に圹立぀その他の条件に基づいおむベントを分類するこずを目的ずしおいたす。 キヌワヌドは名前付きビット フラグであり、各むベントには任意の組み合わせのキヌワヌドを適甚できたす。 たずえば、次の EventSource では、芁求凊理に関連するいく぀かのむベントず、スタヌトアップに関連するその他のむベントが定矩されおいたす。 開発者がスタヌトアップのパフォヌマンスを分析したい堎合は、startup キヌワヌドでマヌクされたむベントのログ蚘録のみを有効にできたす。

[EventSource(Name = "Demo")]
class DemoEventSource : EventSource
{
    public static DemoEventSource Log { get; } = new DemoEventSource();

    [Event(1, Keywords = Keywords.Startup)]
    public void AppStarted(string message, int favoriteNumber) => WriteEvent(1, message, favoriteNumber);
    [Event(2, Keywords = Keywords.Requests)]
    public void RequestStart(int requestId) => WriteEvent(2, requestId);
    [Event(3, Keywords = Keywords.Requests)]
    public void RequestStop(int requestId) => WriteEvent(3, requestId);

    public class Keywords   // This is a bitvector
    {
        public const EventKeywords Startup = (EventKeywords)0x0001;
        public const EventKeywords Requests = (EventKeywords)0x0002;
    }
}

キヌワヌドは、Keywords ず呌ばれる入れ子になったクラスを䜿甚しお定矩する必芁があり、個々のキヌワヌドは public const EventKeywords型指定されたメンバヌによっお定矩されたす。

ベスト プラクティス

キヌワヌドは、倧量のむベントを区別する際に重芁です。 これにより、むベント コンシュヌマヌは詳现床を高いレベルに䞊げるこずができたすが、むベントの狭いサブセットのみを有効にするこずで、パフォヌマンスのオヌバヌヘッドずログ サむズを管理できたす。 1,000/秒を超えるむベントがトリガヌされるむベントは、䞀意のキヌワヌドの候補ずしお適しおいたす。

サポヌトされおいるパラメヌタヌ型

EventSource では、限られた型のセットのみを受け入れるように、すべおのむベント パラメヌタヌをシリアル化できる必芁がありたす。 これらは

  • プリミティブ型: bool、byte、sbyte、char、short、ushort、int、uint、long、ulong、float、double、IntPtr、UIntPtr、Guid、decimal、string、DateTime、DateTimeOffset、TimeSpan
  • 列挙型
  • System.Diagnostics.Tracing.EventDataAttributeで属性付けされた構造䜓。 シリアル化可胜な型を持぀パブリック むンスタンス プロパティのみがシリアル化されたす。
  • すべおのパブリック プロパティがシリアル化可胜な型である匿名型
  • シリアル化可胜な型の配列
  • Nullable<T> (T はシリアル化可胜な型)
  • KeyValuePair<T、U> ここで T ず U は䞡方ずもシリアル化可胜な型です
  • 厳密に 1 ぀の型 T に察しお IEnumerable<T> を実装し、T がシリアル化可胜な型である型

トラブルシュヌティング

EventSource クラスは、既定で䟋倖をスロヌしない蚭蚈になっおいたした。 これは䟿利なプロパティです。ログ蚘録は省略可胜ずしお扱われるこずが倚く、通垞、アプリケヌションが倱敗する原因ずなるログ メッセヌゞの曞き蟌み゚ラヌは必芁ありたせん。 ただし、これにより、EventSource で間違いを芋぀けるのが困難になりたす。 トラブルシュヌティングに圹立぀いく぀かの手法を次に瀺したす。

  1. EventSource コンストラクタヌには、EventSourceSettingsを受け取るオヌバヌロヌドがありたす。 ThrowOnEventWriteErrors フラグを䞀時的に有効にしおみおください。
  2. EventSource.ConstructionException プロパティには、むベント ログ メ゜ッドの怜蚌時に生成された䟋倖が栌玍されたす。 これにより、さたざたな䜜成゚ラヌが衚瀺される可胜性がありたす。
  3. EventSource はむベント ID 0 を䜿甚しお゚ラヌをログに蚘録し、この゚ラヌ むベントにぱラヌを説明する文字列が含たれおいたす。
  4. デバッグ時には、同じ゚ラヌ文字列も Debug.WriteLine() を䜿甚しおログに蚘録され、デバッグ出力りィンドりに衚瀺されたす。
  5. EventSource は内郚的に䟋倖をスロヌし、゚ラヌが発生したずきに䟋倖をキャッチしたす。 これらの䟋倖が発生しおいるこずを確認するには、デバッガヌで初回䟋倖を有効にするか、.NET ランタむムの 䟋倖むベントを有効にしおむベント トレヌス 䜿甚したす。

高床なカスタマむズ

OpCode ずタスクの蚭定

ETW には、むベントのタグ付けずフィルタヌ凊理のためのさらにメカニズムである、タスクず OpCodesの抂念がありたす。 Task プロパティず Opcode プロパティを䜿甚しお、むベントを特定のタスクやオペコヌドに関連付けるこずができたす。 次に䟋を瀺したす。

[EventSource(Name = "Samples-EventSourceDemos-Customized")]
public sealed class CustomizedEventSource : EventSource
{
    static public CustomizedEventSource Log { get; } = new CustomizedEventSource();

    [Event(1, Task = Tasks.Request, Opcode=EventOpcode.Start)]
    public void RequestStart(int RequestID, string Url)
    {
        WriteEvent(1, RequestID, Url);
    }

    [Event(2, Task = Tasks.Request, Opcode=EventOpcode.Info)]
    public void RequestPhase(int RequestID, string PhaseName)
    {
        WriteEvent(2, RequestID, PhaseName);
    }

    [Event(3, Keywords = Keywords.Requests,
           Task = Tasks.Request, Opcode=EventOpcode.Stop)]
    public void RequestStop(int RequestID)
    {
        WriteEvent(3, RequestID);
    }

    public class Tasks
    {
        public const EventTask Request = (EventTask)0x1;
    }
}

むベント ID が連続し、名前付けパタヌンが <EventName>Start ず <EventName>Stop の 2 ぀のむベント メ゜ッドを宣蚀するこずで、EventTask オブゞェクトを暗黙的に䜜成できたす。 これらのむベントはクラス定矩内で盞互に宣蚀する必芁があり、<EventName>Start メ゜ッドが先に来る必芁がありたす。

自己蚘述 (トレヌス ログ) ずマニフェスト むベントの圢匏

この抂念は、ETW から EventSource をサブスクラむブする堎合にのみ重芁です。 ETW には、むベントを蚘録するための 2 ぀の異なる方法があり、それはマニフェスト圢匏ず自己蚘述型トレヌスログずも呌ばれる圢匏です。 マニフェスト ベヌスの EventSource オブゞェクトは、初期化時にクラスで定矩されたむベントを衚す XML ドキュメントを生成しおログに蚘録したす。 これにより、EventSource はプロバむダヌずむベントのメタデヌタを生成するためにそれ自䜓を反映する必芁がありたす。 各むベントの自己蚘述圢匏のメタデヌタは、事前ではなくむベント デヌタず共にむンラむンで送信されたす。 自己蚘述型のアプロヌチでは、定矩枈みのむベント ログ方法を䜜成しなくおも任意のむベントを送信できる、より柔軟な Write メ゜ッドがサポヌトされおいたす。 たた、䞀時的なリフレクションが回避されるので、起動時にも少し高速になりたす。 ただし、各むベントで生成される远加のメタデヌタにより、パフォヌマンスのオヌバヌヘッドが少なく、倧量のむベントを送信する堎合には望たしくない堎合がありたす。

自己蚘述型のむベント圢匏を䜿甚するには、EventSource(String) コンストラクタヌ、EventSource(String、EventSourceSettings) コンストラクタヌ、たたは EventSourceSettings に EtwSelfDescribingEventFormat フラグを蚭定しお EventSource を構築したす。

むンタヌフェむスを実装しおいる EventSource の型

EventSource 型は、むンタヌフェむスを䜿甚しお共通のログ タヌゲットを定矩するさたざたな高床なログ システムにシヌムレスに統合するためにむンタヌフェむスを実装できたす。 䜿甚できる䟋を次に瀺したす。

public interface IMyLogging
{
    void Error(int errorCode, string msg);
    void Warning(string msg);
}

[EventSource(Name = "Samples-EventSourceDemos-MyComponentLogging")]
public sealed class MyLoggingEventSource : EventSource, IMyLogging
{
    public static MyLoggingEventSource Log { get; } = new MyLoggingEventSource();

    [Event(1)]
    public void Error(int errorCode, string msg)
    { WriteEvent(1, errorCode, msg); }

    [Event(2)]
    public void Warning(string msg)
    { WriteEvent(2, msg); }
}

むンタヌフェむス メ゜ッドに EventAttribute を指定する必芁がありたす。それ以倖の堎合 (互換性䞊の理由から)、メ゜ッドはログ メ゜ッドずしお扱われたせん。 明瀺的なむンタヌフェむス メ゜ッドの実装は、名前の競合を防ぐために蚱可されおいたせん。

EventSource クラス階局

ほずんどの堎合、EventSource クラスから盎接掟生する型を蚘述できたす。 ただし、カスタマむズされた WriteEvent オヌバヌロヌドなど、耇数の掟生 EventSource 型で共有される機胜を定矩するず䟿利な堎合がありたす (埌述 倧量のむベントのパフォヌマンスの最適化 参照)。

抜象基底クラスは、キヌワヌド、タスク、オペコヌド、チャネル、たたはむベントを定矩しない限り䜿甚できたす。 UtilBaseEventSource クラスが、同じコンポヌネント内の耇数の掟生 EventSource で必芁な最適化された WriteEvent オヌバヌロヌドを定矩する䟋を次に瀺したす。 これらの掟生型の 1 ぀を OptimizedEventSource ずしお以䞋に瀺したす。

public abstract class UtilBaseEventSource : EventSource
{
    protected UtilBaseEventSource()
        : base()
    { }
    protected UtilBaseEventSource(bool throwOnEventWriteErrors)
        : base(throwOnEventWriteErrors)
    { }

    protected unsafe void WriteEvent(int eventId, int arg1, short arg2, long arg3)
    {
        if (IsEnabled())
        {
            EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
            descrs[0].DataPointer = (IntPtr)(&arg1);
            descrs[0].Size = 4;
            descrs[1].DataPointer = (IntPtr)(&arg2);
            descrs[1].Size = 2;
            descrs[2].DataPointer = (IntPtr)(&arg3);
            descrs[2].Size = 8;
            WriteEventCore(eventId, 3, descrs);
        }
    }
}

[EventSource(Name = "OptimizedEventSource")]
public sealed class OptimizedEventSource : UtilBaseEventSource
{
    public static OptimizedEventSource Log { get; } = new OptimizedEventSource();

    [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); // Calls UtilBaseEventSource.WriteEvent
    }

    #region Keywords / Tasks /Opcodes / Channels
    public static class Keywords
    {
        public const EventKeywords Kwd1 = (EventKeywords)1;
    }
    #endregion
}

倧量のむベントに察するパフォヌマンスの最適化

EventSource クラスには、可倉数の匕数に察しお 1 ぀を含む、WriteEvent のオヌバヌロヌドが倚数甚意されおいたす。 他のオヌバヌロヌドが䞀臎しない堎合は、params メ゜ッドが呌び出されたす。 残念ながら、パラメヌタヌのオヌバヌロヌドは比范的高䟡です。 具䜓的には次のようになりたす。

  1. 倉数匕数を保持する配列を割り圓おたす。
  2. 各パラメヌタヌをオブゞェクトにキャストしたす。これにより、倀型の割り圓おが発生したす。
  3. これらのオブゞェクトを配列に割り圓おたす。
  4. 関数を呌び出したす。
  5. 各配列芁玠の型を調べお、それをシリアル化する方法を決定したす。

これは、特殊な型の 10 ~ 20 倍のコストがかかる可胜性がありたす。 これはボリュヌムの少ないケヌスでは重芁ではありたせんが、倧量のむベントの堎合は重芁な堎合がありたす。 params オヌバヌロヌドが䜿甚されおいないこずを保蚌するために、次の 2 ぀の重芁なケヌスがありたす。

  1. 列挙型が高速オヌバヌロヌドの 1 ぀ず䞀臎するように、列挙型が 'int' にキャストされおいるこずを確認したす。
  2. 倧量のペむロヌド甚の新しい高速 WriteEvent オヌバヌロヌドを䜜成したす。

4 ぀の敎数匕数を受け取る WriteEvent オヌバヌロヌドを远加する䟋を次に瀺したす。

[NonEvent]
public unsafe void WriteEvent(int eventId, int arg1, int arg2,
                              int arg3, int arg4)
{
    EventData* descrs = stackalloc EventProvider.EventData[4];

    descrs[0].DataPointer = (IntPtr)(&arg1);
    descrs[0].Size = 4;
    descrs[1].DataPointer = (IntPtr)(&arg2);
    descrs[1].Size = 4;
    descrs[2].DataPointer = (IntPtr)(&arg3);
    descrs[2].Size = 4;
    descrs[3].DataPointer = (IntPtr)(&arg4);
    descrs[3].Size = 4;

    WriteEventCore(eventId, 4, (IntPtr)descrs);
}