System.Diagnostics.Tracing.EventSource, klasa

Ten artykuł zawiera dodatkowe uwagi dotyczące dokumentacji referencyjnej dla tego interfejsu API.

Klasa EventSource ma być dziedziczona przez klasę użytkownika, która udostępnia określone zdarzenia do użycia na potrzeby śledzenia zdarzeń. Metody EventSource.WriteEvent są wywoływane w celu rejestrowania zdarzeń.

Podstawowa funkcjonalność EventSource programu jest wystarczająca dla większości aplikacji. Jeśli chcesz mieć większą kontrolę nad utworzonymi metadanymi zdarzeń, możesz zastosować EventAttribute atrybut do metod. W przypadku zaawansowanych aplikacji źródła zdarzeń można przechwycić polecenia wysyłane do źródła zdarzeń pochodnych i zmienić filtrowanie lub spowodować akcje (takie jak dumping struktury danych) wykonywane przez dziedziczący. Źródło zdarzeń można aktywować w procesie przy użyciu i poza procesem przy użyciu EventListener narzędzi opartych na technologii EventPipe, takich jak lub śledzenie zdarzeń dla narzędzi opartych na systemie Windows (ETW), takich jak dotnet-tracePerfView lub Logman. Istnieje również możliwość programowego sterowania i przechwytywania dyspozytora danych. Klasa EventListener zapewnia dodatkowe funkcje.

Konwencje

EventSource-pochodne klasy powinny być zgodne z następującymi konwencjami:

  • Klasy zdefiniowane przez użytkownika powinny implementować pojedynczy wzorzec. Wystąpienie pojedynczego wystąpienia ma tradycyjnie nazwę Log. Przez rozszerzenie użytkownicy nie powinni wywoływać IDisposable.Dispose ręcznie i zezwalać środowisku uruchomieniowemu na czyszczenie pojedynczego wystąpienia na końcu wykonywania kodu zarządzanego.
  • Klasa pochodna zdefiniowana przez użytkownika powinna być oznaczona jako sealed , chyba że implementuje zaawansowaną konfigurację "Narzędzie EventSource" omówioną w sekcji Zaawansowane użycie.
  • Przed wykonaniem jakichkolwiek prac intensywnie korzystających z zasobów związanych z wypaleniem zdarzenia należy wywołać IsEnabled() połączenie.
  • Obiekty można tworzyć EventTask niejawnie, deklarując dwie metody zdarzeń przy użyciu kolejnych identyfikatorów zdarzeń, które mają wzorzec <EventName>Start nazewnictwa i <EventName>Stop. Te zdarzenia muszą być deklarowane obok siebie w definicji klasy, a <EventName>Start metoda musi być najpierw.
  • Próba zachowania EventSource zgodności obiektów z poprzednimi wersjami i odpowiednie ich wersje. Domyślną wersją zdarzenia jest 0. Wersję można zmienić, ustawiając wartość Version. Zmień wersję zdarzenia za każdym razem, gdy zmienisz właściwości ładunku. Zawsze dodaj nowe właściwości ładunku na końcu deklaracji zdarzenia. Jeśli nie jest to możliwe, utwórz nowe zdarzenie o nowym identyfikatorze, aby zastąpić stary.
  • Podczas deklarowania metod zdarzeń określ właściwości ładunku o stałym rozmiarze przed właściwościami o zmiennym rozmiarze.
  • EventKeywords są używane jako maska bitowa do określania określonych zdarzeń podczas subskrybowania dostawcy. Słowa kluczowe można określić, definiując klasę składową zawierającą public static class Keywordspublic const EventKeywords elementy członkowskie.
  • Kojarzenie kosztownych zdarzeń za EventKeywords pomocą polecenia EventAttribute. Ten wzorzec umożliwia użytkownikom EventSource rezygnację z tych kosztownych operacji.

Self-describing (tracelogging) vs manifest event formats (formaty zdarzeń manifestu)

EventSourceMożna skonfigurować do dwóch różnych trybów na podstawie używanego konstruktora lub flag ustawionych na .EventSourceOptions

Historycznie te dwa formaty pochodzą z dwóch formatów używanych przez śledzenie zdarzeń dla systemu Windows (ETW). Chociaż te dwa tryby nie mają wpływu na możliwość korzystania z śledzenia zdarzeń dla odbiorników systemu Windows (ETW) lub eventPipe, generują metadane dla zdarzeń inaczej.

Domyślny format zdarzenia to EtwManifestEventFormat, który jest ustawiany, jeśli nie jest określony na .EventSourceSettings Obiekty oparte na EventSource manifeście generują dokument XML reprezentujący zdarzenia zdefiniowane w klasie podczas inicjowania. Wymaga to odzwierciedlenia EventSource samego siebie w celu wygenerowania dostawcy i metadanych zdarzeń.

Aby użyć formatu zdarzeń samodzielnego opisywania (śledzenia), skonstruuj EventSource przy użyciu EventSource(String) konstruktora, konstruktora EventSource(String, EventSourceSettings) lub przez ustawienie flagi EtwSelfDescribingEventFormat na .EventSourceSettings Źródła samoopisujące generują minimalne metadane dostawcy podczas inicjowania i generują metadane zdarzeń tylko wtedy, gdy Write(String) jest wywoływana.

W praktyce te ustawienia formatu zdarzeń mają wpływ tylko na użycie z czytnikami w oparciu o śledzenie zdarzeń dla systemu Windows (ETW). Mogą one jednak mieć niewielki wpływ na czas inicjowania i czas zapisu poszczególnych zdarzeń ze względu na czas wymagany do odbicia i generowania metadanych.

Przykłady

W poniższym przykładzie przedstawiono prostą implementację EventSource klasy.

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

W poniższym przykładzie przedstawiono bardziej złożoną implementację EventSource klasy.

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

Użycie zaawansowane

Tradycyjnie obiekty zdefiniowane przez EventSource użytkownika oczekują dziedziczenia bezpośrednio z EventSourceklasy . W przypadku zaawansowanych scenariuszy można jednak tworzyć abstractEventSource obiekty o nazwie Źródła narzędzi i implementować interfejsy. Użycie jednej lub obu tych technik umożliwia udostępnianie kodu między różnymi źródłami pochodnymi.

Ważne

Obiekty abstrakcyjne EventSource nie mogą definiować słów kluczowych, zadań, kodów operacji, kanałów lub zdarzeń.

Ważne

Aby uniknąć kolizji nazw w czasie wykonywania podczas generowania metadanych zdarzeń, nie implementuj jawnie metod interfejsu podczas korzystania z interfejsów z EventSourceprogramem .

W poniższym przykładzie pokazano implementację EventSource , która używa interfejsu.

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

W poniższym przykładzie pokazano implementację, EventSource która używa wzorca Utility 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!
}

Poniższy przykład przedstawia implementację EventSource funkcji śledzenia informacji o składniku w bibliotece.

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