Classe System.Diagnostics.Tracing.EventSource

Cet article vous offre des remarques complémentaires à la documentation de référence pour cette API.

La EventSource classe est destinée à être héritée par une classe utilisateur qui fournit des événements spécifiques à utiliser pour le suivi des événements. Les EventSource.WriteEvent méthodes sont appelées pour journaliser les événements.

La fonctionnalité de base est EventSource suffisante pour la plupart des applications. Si vous souhaitez plus de contrôle sur les métadonnées d’événement créées, vous pouvez appliquer l’attribut EventAttribute aux méthodes. Pour les applications sources d’événements avancées, il est possible d’intercepter les commandes envoyées à la source d’événement dérivée et de modifier le filtrage, ou de provoquer l’exécution d’actions (telles que la suppression d’une structure de données) par l’héritier. Une source d’événement peut être activée en cours à l’aide EventListener et hors processus à l’aide d’outils basés sur EventPipe, tels que ou des dotnet-trace outils basés sur le suivi d’événements pour Windows (ETW) comme PerfView ou Logman. Il est également possible de contrôler et d’intercepter par programmation le répartiteur de données. La EventListener classe fournit des fonctionnalités supplémentaires.

Conventions

EventSourceLes classes dérivées doivent suivre les conventions suivantes :

  • Les classes définies par l’utilisateur doivent implémenter un modèle singleton. L’instance singleton est traditionnellement nommée Log. Par extension, les utilisateurs ne doivent pas appeler IDisposable.Dispose manuellement et autoriser le runtime à propre l’instance singleton à la fin de l’exécution du code managé.
  • Une classe dérivée définie par l’utilisateur doit être marquée comme sealed étant, sauf si elle implémente la configuration avancée « Utility EventSource » décrite dans la section Utilisation avancée.
  • Appel IsEnabled() avant d’effectuer un travail gourmand en ressources lié au déclenchement d’un événement.
  • Vous pouvez créer implicitement des EventTask objets en déclarant deux méthodes d’événement avec les ID d’événement suivants qui ont le modèle <EventName>Start de nommage et <EventName>Stop. Ces événements doivent être déclarés en regard des uns des autres dans la définition de classe et la <EventName>Start méthode doit commencer.
  • Essayez de maintenir EventSource les objets à compatibilité descendante et de les versionr correctement. La version par défaut d’un événement est 0. La version peut être modifiée en définissant Version. Modifiez la version d’un événement chaque fois que vous modifiez les propriétés de la charge utile. Ajoutez toujours de nouvelles propriétés de charge utile à la fin de la déclaration d’événement. Si cela n’est pas possible, créez un événement avec un nouvel ID pour remplacer l’ancien.
  • Lors de la déclaration de méthodes d’événement, spécifiez les propriétés de charge utile de taille fixe avant les propriétés de taille variable.
  • EventKeywords sont utilisés comme masque de bits pour spécifier des événements spécifiques lors de l’abonnement à un fournisseur. Vous pouvez spécifier des mot clé en définissant une public static class Keywords classe membre qui a public const EventKeywords des membres.
  • Associer des événements coûteux à une EventKeywords utilisation EventAttribute. Ce modèle permet aux utilisateurs de votre EventSource choix de refuser ces opérations coûteuses.

Formats d’événements autodescriptifs (journalisation de trace) et de manifeste

EventSource peut être configuré en deux modes différents en fonction du constructeur utilisé ou des indicateurs définis sur EventSourceOptions.

Historiquement, ces deux formats sont dérivés de deux formats utilisés par le suivi des événements pour Windows (ETW). Bien que ces deux modes n’affectent pas votre capacité à utiliser le suivi des événements pour Windows (ETW) ou les écouteurs Basés sur EventPipe, ils génèrent les métadonnées pour les événements différemment.

Le format d’événement par défaut est , qui est EtwManifestEventFormatdéfini s’il n’est pas spécifié sur EventSourceSettings. Les objets basés sur EventSource un manifeste génèrent un document XML représentant les événements définis sur la classe lors de l’initialisation. Cela nécessite la EventSource réflexion sur elle-même pour générer les métadonnées du fournisseur et des événements.

Pour utiliser le format d’événement auto-décrivant (tracelogging), construisez-le EventSource à l’aide du EventSource(String) constructeur, du EventSource(String, EventSourceSettings) constructeur ou en définissant l’indicateur EtwSelfDescribingEventFormat sur EventSourceSettings. Les sources auto-décrivant génèrent des métadonnées de fournisseur minimales lors de l’initialisation et génèrent uniquement des métadonnées d’événement lorsqu’elles Write(String) sont appelées.

Dans la pratique, ces paramètres de format d’événement affectent uniquement l’utilisation avec les lecteurs en fonction du suivi des événements pour Windows (ETW). Toutefois, ils peuvent avoir un petit effet sur le temps d’initialisation et les heures d’écriture par événement en raison du temps nécessaire à la réflexion et à la génération des métadonnées.

Exemples

L’exemple suivant montre une implémentation simple de la EventSource classe.

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

L’exemple suivant montre une implémentation plus complexe de la EventSource classe.

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

Utilisation avancée

Traditionnellement, les objets définis par EventSource l’utilisateur s’attendent à hériter directement de EventSource. Toutefois, pour les scénarios avancés, vous pouvez créer des abstractEventSource objets, appelés Sources utilitaires et implémenter des interfaces. L’utilisation d’une ou de ces deux techniques vous permet de partager du code entre différentes sources dérivées.

Important

Les objets abstraits EventSource ne peuvent pas définir des mot clé, des tâches, des opcodes, des canaux ou des événements.

Important

Pour éviter les collisions de noms au moment de l’exécution lors de la génération de métadonnées d’événement, n’implémentez pas explicitement les méthodes d’interface lors de l’utilisation d’interfaces avec EventSource.

L’exemple suivant montre une implémentation qui EventSource utilise une interface.

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);
}

L’exemple suivant montre une implémentation qui EventSource utilise le modèle EventSource de l’utilitaire.

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!
}

L’exemple suivant montre une implémentation d’informations de EventSource suivi sur un composant d’une bibliothèque.

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);
            }
        }
    }
}