Classe System.Diagnostics.Tracing.EventSource

Observação

Este artigo fornece observações complementares à documentação de referência para esta API.

A EventSource classe destina-se a ser herdada por uma classe de usuário que fornece eventos específicos a serem usados para rastreamento de eventos. Os EventSource.WriteEvent métodos são chamados para registrar os eventos.

A funcionalidade básica do EventSource é suficiente para a maioria das aplicações. Se quiser mais controle sobre os metadados de evento criados, você pode aplicar o EventAttribute atributo aos métodos. Para aplicativos avançados de origem de eventos, é possível intercetar os comandos que estão sendo enviados para a fonte de eventos derivada e alterar a filtragem, ou fazer com que ações (como despejar uma estrutura de dados) sejam executadas pelo herdeiro. Uma fonte de eventos pode ser ativada em processo usando EventListener e fora do processo usando ferramentas baseadas em EventPipe, como dotnet-trace ou ferramentas baseadas em Rastreamento de Eventos para Windows (ETW), como PerfView ou Logman. Também é possível controlar e intercetar programaticamente o despachante de dados. A EventListener classe fornece funcionalidade adicional.

Convenções

EventSource-As classes derivadas devem seguir as seguintes convenções:

  • As classes definidas pelo usuário devem implementar um padrão singleton. A instância singleton é tradicionalmente chamada Log. Consequentemente, os usuários não devem chamar IDisposable.Dispose manualmente e devem permitir que o runtime limpe a instância singleton no final da execução do código gerido.
  • Uma classe derivada definida pelo usuário deve ser marcada como sealed a menos que implemente a configuração avançada "Utility EventSource" discutida na seção Uso Avançado.
  • Chame IsEnabled() antes de executar qualquer trabalho intensivo em termos de recursos associado ao disparo de um evento.
  • Você pode criar objetos EventTask implicitamente ao declarar dois métodos de evento com IDs de evento subsequentes que sigam o padrão de nomenclatura <EventName>Start e <EventName>Stop. Esses eventos devem ser declarados um ao lado do outro na definição de classe e o <EventName>Start método deve vir primeiro.
  • Tente manter os objetos EventSource compatíveis com versões anteriores e versioná-los de forma adequada. A versão padrão para um evento é 0. A versão pode ser alterada pela configuração Version. Altere a versão de um evento sempre que alterar as propriedades da carga útil. Sempre adicione novas propriedades de carga útil ao final da declaração de evento. Se isso não for possível, crie um novo evento com uma nova ID para substituir o antigo.
  • Ao declarar métodos de evento, especifique propriedades de carga útil de tamanho fixo antes de propriedades de tamanho variável.
  • EventKeywords são usados como uma máscara de bits para especificar determinados eventos ao subscrever a um fornecedor. Você pode especificar palavras-chave definindo uma public static class Keywords classe de membro que tenha public const EventKeywords membros.
  • Associe eventos caros a um EventKeywords utilizando EventAttribute. Esse padrão permite que os usuários do seu EventSource optem por não participar dessas operações caras.

Formatos autodescritivos (tracelogging) vs. formatos de evento com manifesto

EventSource pode ser configurado em dois modos diferentes com base no construtor usado ou em quais sinalizadores estão definidos em EventSourceOptions.

Historicamente, esses dois formatos são derivados de dois formatos que o Rastreamento de Eventos para Windows (ETW) usou. Embora esses dois modos não afetem sua capacidade de usar o Rastreamento de Eventos para Windows (ETW) ou ouvintes baseados em EventPipe, eles geram os metadados para eventos de forma diferente.

O formato de evento padrão é EtwManifestEventFormat, que é definido se não for especificado em EventSourceSettings. Os objetos baseados em EventSource manifesto geram um documento XML que representa os eventos definidos na classe após a inicialização. Este requer que o EventSource reflita sobre si mesmo para gerar o provedor e os metadados do evento.

Para usar o formato de evento autodescritivo (tracelogging), construa o seu EventSource usando o construtor EventSource(String) ou o construtor EventSource(String, EventSourceSettings), ou definindo o indicador EtwSelfDescribingEventFormat em EventSourceSettings. As fontes autodescritivas geram metadados mínimos do provedor na inicialização e só geram metadados de eventos quando Write(String) são chamados. Ao contrário do formato baseado em manifesto, ao ouvir via ETW, apenas os metadados Level, Keyword e Opcode são incluídos no EventAttribute atributo. Outras propriedades como EventId ou Message não estão incluídas.

Na prática, essas configurações de formato de evento afetam apenas o uso com leitores baseados no Rastreamento de Eventos para Windows (ETW). Eles podem, no entanto, ter um pequeno efeito no tempo de inicialização e nos tempos de gravação por evento devido ao tempo necessário para reflexão e geração dos metadados.

Exemplos

O exemplo a seguir mostra uma implementação simples da 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

O exemplo a seguir mostra uma implementação mais complexa da 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

Utilização avançada

Tradicionalmente, os objetos definidos pelo usuário EventSource esperam herdar diretamente de EventSource. Para cenários avançados, no entanto, pode-se criar objetos abstractEventSource, chamados Fontes Utilitárias, e implementar interfaces. O uso de uma ou ambas as técnicas permite que você compartilhe código entre diferentes fontes derivadas.

Importante

Os objetos abstratos EventSource não podem definir palavras-chave, tarefas, opcodes, canais ou eventos.

Importante

Para evitar colisões de nomes em tempo de execução ao gerar metadados de eventos, não implemente explicitamente métodos de interface ao usar interfaces com EventSource.

O exemplo a seguir mostra a implementação de EventSource que usa uma 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);
}

O exemplo a seguir mostra uma implementação de EventSource que usa o padrão 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!
}

O exemplo a seguir mostra uma implementação de EventSource para rastrear informações sobre um componente em uma biblioteca.

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