Condividi tramite


Classe System.Diagnostics.Tracing.EventSource

Questo articolo fornisce osservazioni supplementari alla documentazione di riferimento per questa API.

La EventSource classe deve essere ereditata da una classe utente che fornisce eventi specifici da usare per la traccia di eventi. I EventSource.WriteEvent metodi vengono chiamati per registrare gli eventi.

La funzionalità di base di è sufficiente per la maggior parte delle EventSource applicazioni. Se si vuole un maggiore controllo sui metadati dell'evento creati, è possibile applicare l'attributo EventAttribute ai metodi. Per le applicazioni di origine eventi avanzate, è possibile intercettare i comandi inviati all'origine evento derivata e modificare il filtro oppure per fare in modo che le azioni (ad esempio il dump di una struttura di dati) vengano eseguite dall'ereditatore. Un'origine evento può essere attivata in-process usando EventListener e out-of-process usando strumenti basati su EventPipe come dotnet-trace o strumenti basati su Event Tracing for Windows (ETW) come PerfView o Logman. È anche possibile controllare e intercettare a livello di codice il dispatcher dati. La EventListener classe fornisce funzionalità aggiuntive.

Convenzioni

EventSourceLe classi derivate da devono seguire le convenzioni seguenti:

  • Le classi definite dall'utente devono implementare un modello singleton. L'istanza singleton è tradizionalmente denominata Log. Per estensione, gli utenti non devono chiamare IDisposable.Dispose manualmente e consentire al runtime di pulire l'istanza singleton alla fine dell'esecuzione del codice gestito.
  • Una classe derivata definita dall'utente deve essere contrassegnata come sealed a meno che non implementi la configurazione avanzata "Utility EventSource" descritta nella sezione Utilizzo avanzato.
  • Chiamare IsEnabled() prima di eseguire qualsiasi lavoro a elevato utilizzo di risorse correlato alla generazione di un evento.
  • È possibile creare EventTask oggetti in modo implicito dichiarando due metodi evento con ID evento successivi con il modello <EventName>Start di denominazione e <EventName>Stop. Questi eventi devono essere dichiarati uno accanto all'altro nella definizione della classe e il <EventName>Start metodo deve venire per primo.
  • Tentare di mantenere EventSource gli oggetti compatibili con le versioni precedenti e di eseguirne la versione in modo appropriato. La versione predefinita per un evento è 0. La versione può essere modificata impostando Version. Modificare la versione di un evento ogni volta che si modificano le proprietà del payload. Aggiungere sempre nuove proprietà del payload alla fine della dichiarazione di evento. Se non è possibile, creare un nuovo evento con un nuovo ID per sostituire quello precedente.
  • Quando si dichiarano metodi di evento, specificare le proprietà del payload a dimensione fissa prima di ridimensionare in modo variabile le proprietà.
  • EventKeywords vengono usati come maschera di bit per specificare eventi specifici durante la sottoscrizione a un provider. È possibile specificare parole chiave definendo una public static class Keywords classe membro con public const EventKeywords membri.
  • Associare eventi costosi a un EventKeywords oggetto tramite EventAttribute. Questo modello consente agli utenti dell'utente di EventSource rifiutare esplicitamente queste operazioni costose.

Formati di eventi autodescrittivo (tracelogging) e manifesto

EventSource può essere configurato in in due modalità diverse in base al costruttore usato o ai flag impostati su EventSourceOptions.

Storicamente, questi due formati derivano da due formati usati da Event Tracing for Windows (ETW). Anche se queste due modalità non influiscono sulla possibilità di usare Event Tracing for Windows (ETW) o listener basati su EventPipe, generano i metadati per gli eventi in modo diverso.

Il formato di evento predefinito è EtwManifestEventFormat, che viene impostato se non specificato in EventSourceSettings. Gli oggetti basati su EventSource manifesto generano un documento XML che rappresenta gli eventi definiti nella classe al momento dell'inizializzazione. È quindi necessario che l'oggetto EventSource rifletta su se stesso per generare il provider e i metadati dell'evento.

Per usare il formato di evento autodescrittura (tracelogging), costruire EventSource utilizzando il EventSource(String) costruttore, il EventSource(String, EventSourceSettings) costruttore o impostando il EtwSelfDescribingEventFormat flag su EventSourceSettings. Le origini autodescrittura generano metadati minimi del provider per l'inizializzazione e generano metadati degli eventi solo quando Write(String) viene chiamato.

In pratica, queste impostazioni del formato di evento influiscono solo sull'utilizzo con i lettori basati su Event Tracing for Windows (ETW). Tuttavia, possono avere un piccolo effetto sul tempo di inizializzazione e sui tempi di scrittura per evento a causa del tempo necessario per la reflection e la generazione dei metadati.

Esempi

Nell'esempio seguente viene illustrata una semplice implementazione della 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

Nell'esempio seguente viene illustrata un'implementazione più complessa della 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

Uso avanzato

Tradizionalmente, gli oggetti definiti dall'utente EventSource si aspettano di ereditare direttamente da EventSource. Per gli scenari avanzati, tuttavia, è possibile creare abstract EventSource oggetti, denominati Origini utilità e implementare interfacce. L'uso di una o entrambe queste tecniche consente di condividere il codice tra origini derivate diverse.

Importante

Gli oggetti astratti EventSource non possono definire parole chiave, attività, opcode, canali o eventi.

Importante

Per evitare conflitti di nomi in fase di esecuzione durante la generazione dei metadati degli eventi, non implementare in modo esplicito i metodi di interfaccia quando si usano interfacce con EventSource.

Nell'esempio seguente viene illustrata un'implementazione di EventSource che usa un'interfaccia .

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

Nell'esempio seguente viene illustrata un'implementazione di EventSource che usa il modello EventSource dell'utilità.

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

Nell'esempio seguente viene illustrata un'implementazione di EventSource per tracciare informazioni su un componente in una libreria.

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