Класс System.Diagnostics.Tracing.EventSource

В этой статье приводятся дополнительные замечания к справочной документации по этому API.

Класс EventSource должен наследоваться пользовательским классом, предоставляющим определенные события для трассировки событий. Методы EventSource.WriteEvent вызываются для регистрации событий.

Базовые функциональные возможности EventSource достаточно для большинства приложений. Если требуется больше контроля над созданными метаданными события, можно применить EventAttribute атрибут к методам. Для расширенных исходных приложений событий можно перехватывать команды, отправляемые в источник производного события, и изменять фильтрацию или вызывать действия (например, дамп структуры данных) для выполнения наследутелем. Источник событий можно активировать с помощью и внепроцессного использования EventListener с помощью таких средств на основе EventPipe, как dotnet-trace или трассировка событий для Windows (ETW).PerfView Logman Кроме того, можно программно контролировать и перехватывать диспетчер данных. Класс EventListener предоставляет дополнительные функциональные возможности.

Соглашения

EventSourceПроизводные классы должны соответствовать следующим соглашениям:

  • Определяемые пользователем классы должны реализовывать единый шаблон. Одноэлементный экземпляр традиционно называется Log. По расширению пользователи не IDisposable.Dispose должны вызывать вручную и разрешать среде выполнения очистку одноэлементного экземпляра в конце выполнения управляемого кода.
  • Определяемый пользователем производный класс должен быть помечен как sealed если он не реализует расширенную конфигурацию "Служебный eventSource", описанную в разделе "Дополнительное использование".
  • Вызов IsEnabled() перед выполнением любой ресурсоемкой работы, связанной с запуском события.
  • Можно неявно создавать EventTask объекты, объявляя два метода события с последующими идентификаторами событий, имеющими шаблон <EventName>Start именования и <EventName>Stop. Эти события должны быть объявлены рядом друг с другом в определении класса, и <EventName>Start метод должен быть первым.
  • Попытайтесь обеспечить EventSource обратную совместимость объектов и их версию соответствующим образом. Версия по умолчанию для события 0. Версию можно изменить с помощью параметра Version. Изменяйте версию события при изменении свойств полезных данных. Всегда добавляйте новые свойства полезных данных в конец объявления события. Если это невозможно, создайте событие с новым идентификатором, чтобы заменить старый.
  • При объявлении методов событий укажите свойства полезных данных фиксированного размера перед изменяющимися значениями.
  • EventKeywords используется в качестве битовой маски для указания определенных событий при подписке на поставщика. Ключевые слова можно указать, определив public static class Keywords класс-член с public const EventKeywords элементами.
  • Связывание дорогостоящих EventKeywords событий с использованием EventAttribute. Этот шаблон позволяет пользователям отказаться EventSource от этих дорогостоящих операций.

Самоописание (трассировка) и форматы событий манифеста

EventSource можно настроить для двух разных режимов на основе конструктора, используемого или на основе флагов EventSourceOptions.

Исторически эти два формата являются производными от двух форматов, используемых трассировкой событий для Windows (ETW). Хотя эти два режима не влияют на возможность использования трассировки событий для Windows (ETW) или прослушивателей на основе EventPipe, они создают метаданные для событий по-разному.

Формат события по умолчанию, EtwManifestEventFormatкоторый устанавливается, если он не указан EventSourceSettings. Объекты на основе EventSource манифеста создают XML-документ, представляющий события, определенные в классе при инициализации. Это требует EventSource отражать себя для создания поставщика и метаданных событий.

Чтобы использовать формат событий с самостоятельным описанием (tracelogging), создайте EventSource EventSource(String) EventSource(String, EventSourceSettings) конструктор, конструктор или задав EtwSelfDescribingEventFormat флаг EventSourceSettings. Самостоятельно описывающие источники создают минимальные метаданные поставщика при инициализации и создают только метаданные события при 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. Однако для расширенных сценариев можно создавать abstract EventSource объекты, называемые источниками служебной программы, и реализовывать интерфейсы. Использование одного или обоих из этих методов позволяет совместно использовать код между различными производными источниками.

Внимание

Абстрактные 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);
}

В следующем примере показана реализация EventSource , которая использует шаблон 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!
}

В следующем примере показана реализация сведений 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);
            }
        }
    }
}