Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Este artigo se aplica a: ✔️ .NET Core 3.1 e versões posteriores ✔️ do .NET Framework 4.5 e versões posteriores
O guia de início mostrou como criar um EventSource mínimo e coletar eventos em um arquivo de rastreamento. Este tutorial entra em mais detalhes sobre como criar eventos usando System.Diagnostics.Tracing.EventSource.
Um EventSource mínimo
[EventSource(Name = "Demo")]
class DemoEventSource : EventSource
{
public static DemoEventSource Log { get; } = new DemoEventSource();
[Event(1)]
public void AppStarted(string message, int favoriteNumber) => WriteEvent(1, message, favoriteNumber);
}
A estrutura básica de um EventSource derivado é sempre a mesma. Em particular:
- A classe é herdada de System.Diagnostics.Tracing.EventSource
- Para cada tipo diferente de evento que você deseja gerar, um método precisa ser definido. Esse método deve ser nomeado usando o nome do evento que está sendo criado. Se o evento tiver dados adicionais, eles deverão ser passados usando argumentos. Esses argumentos de evento precisam ser serializados para que apenas determinados tipos sejam permitidos.
- Cada método possui um corpo que chama WriteEvent, passando um ID (um valor numérico que representa o evento) e os argumentos do método de evento. A ID precisa ser exclusiva dentro do EventSource. A ID é atribuída explicitamente usando o System.Diagnostics.Tracing.EventAttribute
- Os EventSources devem ser instâncias singleton. Portanto, é conveniente definir uma variável estática, por convenção chamada
Log, que representa esse singleton.
Regras para definir métodos de evento
- Qualquer método que retorne nulo de instância, não virtual, definido em uma classe EventSource é, por padrão, um método de registro em log de eventos.
- Os métodos virtuais ou que não retornam nulos serão incluídos somente se estiverem marcados com o System.Diagnostics.Tracing.EventAttribute
- Para marcar um método qualificado indicando que ele não é de registro em log, acrescente System.Diagnostics.Tracing.NonEventAttribute a ele
- Os métodos de registro em log de eventos têm IDs de evento associadas a eles. Isso pode ser feito explicitamente decorando o método com um System.Diagnostics.Tracing.EventAttribute ou implicitamente pelo número ordinal do método na classe. Por exemplo, usando numeração implícita, o primeiro método na classe tem ID 1, o segundo tem ID 2 e assim por diante.
- Os métodos de registro em log de eventos precisam chamar uma sobrecarga WriteEvent, WriteEventCore, WriteEventWithRelatedActivityId ou WriteEventWithRelatedActivityIdCore.
- A ID do evento, seja implícita ou explícita, deve corresponder ao primeiro argumento passado para a API WriteEvent* que ele chama.
- O número, os tipos e a ordem dos argumentos passados para o método EventSource devem se alinhar com a forma como eles são passados para as APIs WriteEvent*. Para WriteEvent, os argumentos seguem a ID do evento, para WriteEventWithRelatedActivityId, os argumentos seguem a ID da atividade relacionada. Para os métodos WriteEvent*Core, os argumentos devem ser serializados manualmente no
dataparâmetro. - Os nomes de eventos não podem conter
<ou>caracteres. Embora os métodos definidos pelo usuário também não possam conter esses caracteres,asyncos métodos serão reescritos pelo compilador para contê-los. Para garantir que esses métodos gerados não se tornem eventos, marque todos os métodos que não são eventos em um EventSource com o NonEventAttribute.
Práticas recomendadas
- Os tipos que derivam do EventSource geralmente não têm tipos intermediários na hierarquia ou implementam interfaces. Consulte personalizações avançadas abaixo para algumas exceções em que isso pode ser útil.
- Geralmente, o nome da classe EventSource é um nome público inválido para o EventSource. Os nomes públicos, os nomes que aparecerão em configurações de log e visualizadores de log, devem ser globalmente exclusivos. Portanto, é uma boa prática dar ao EventSource um nome público usando o System.Diagnostics.Tracing.EventSourceAttribute. O nome "Demo" usado acima é curto e tem poucas chances de ser exclusivo, portanto, não é uma boa opção para uso em produção. Uma convenção comum é usar um nome hierárquico com
.ou-como separador, como "MyCompany-Samples-Demo", ou o nome do assembly ou namespace para o qual o EventSource fornece eventos. Não é recomendável incluir "EventSource" como parte do nome público. - Atribuir IDs de Evento explicitamente, dessa forma alterações aparentemente benignas ao código na classe de origem, como reorganizar ou adicionar um método no meio, não alterará a ID do evento associada a cada método.
- Ao criar eventos que representam o início e o fim de uma unidade de trabalho, por convenção esses métodos são nomeados com sufixos 'Start' e 'Stop'. Por exemplo, 'RequestStart' e 'RequestStop'.
- Não especifique um valor explícito para a propriedade Guid de EventSourceAttribute, a menos que você precise dela por motivos de compatibilidade com versões anteriores. O valor padrão do Guid é derivado do nome da fonte, o que permite que as ferramentas aceitem o nome mais fácil de ler e derivem o mesmo Guid.
- Chame IsEnabled() antes de executar trabalhos com uso intensivo de recursos relacionados ao disparo de um evento, como a computação de um argumento de evento dispendioso que não é necessário quando o evento está desabilitado.
- Tente manter o objeto EventSource compatível com versões anteriores e defina a versão dele adequadamente. A versão padrão de um evento é 0. A versão pode ser alterada pela configuração EventAttribute.Version. Altere a versão de um evento sempre que você alterar os dados serializados com ele. Sempre adicione novos dados serializados ao final da declaração de evento, ou seja, no final da lista de parâmetros de método. Se isso não for possível, crie um novo evento com uma nova ID para substituir a antiga.
- Ao declarar métodos de eventos, especifique dados de conteúdo de tamanho fixo antes de dados de tamanho variável.
- Não use cadeias de caracteres contendo caracteres nulos. Ao gerar o manifesto para ETW, o EventSource declarará todas as cadeias de caracteres como terminadas com nulo, mesmo que seja possível ter um caractere nulo em uma String em C#. Se uma cadeia de caracteres contiver um caractere nulo, toda a cadeia de caracteres será gravada no conteúdo do evento, mas qualquer analisador tratará o primeiro caractere nulo como o final da cadeia de caracteres. Se houver argumentos de conteúdo após a cadeia de caracteres, o restante da cadeia de caracteres será analisado em vez do valor pretendido.
Personalizações típicas de eventos
Definindo níveis de verbosidade do evento
Cada evento tem um nível de verbosidade e os assinantes de eventos geralmente habilitam todos os eventos em um EventSource até um determinado nível de verbosidade. Os eventos declaram seu nível de verbosidade usando a Level propriedade. Por exemplo, neste EventSource abaixo, um assinante que solicita eventos de nível informativo e inferior, o evento Verbose DebugMessage não é registrado.
[EventSource(Name = "MyCompany-Samples-Demo")]
class DemoEventSource : EventSource
{
public static DemoEventSource Log { get; } = new DemoEventSource();
[Event(1, Level = EventLevel.Informational)]
public void AppStarted(string message, int favoriteNumber) => WriteEvent(1, message, favoriteNumber);
[Event(2, Level = EventLevel.Verbose)]
public void DebugMessage(string message) => WriteEvent(2, message);
}
Se o nível de verbosidade de um evento não for especificado no EventAttribute, ele será definido como Informativo.
Prática recomendada
Use níveis menores que Informativos para avisos ou erros relativamente raros. Quando estiver em dúvida, mantenha o padrão de Informativo e use Verbose para eventos que ocorrem com mais frequência do que 1000 eventos/s.
Definindo palavras-chave de evento
Alguns sistemas de rastreamento de eventos dão suporte a palavras-chave como um mecanismo de filtragem adicional. Ao contrário da verbosidade que categoriza eventos por nível de detalhes, as palavras-chave destinam-se a categorizar eventos com base em outros critérios, como áreas de funcionalidade de código ou que seriam úteis para diagnosticar determinados problemas. As palavras-chave são sinalizadores de bit nomeados e cada evento pode ter qualquer combinação de palavras-chave aplicada. Por exemplo, o EventSource abaixo define alguns eventos relacionados ao processamento de solicitações e outros eventos relacionados à inicialização. Se um desenvolvedor quiser analisar o desempenho da inicialização, ele poderá habilitar apenas o registro em log dos eventos marcados com a palavra-chave de inicialização.
[EventSource(Name = "Demo")]
class DemoEventSource : EventSource
{
public static DemoEventSource Log { get; } = new DemoEventSource();
[Event(1, Keywords = Keywords.Startup)]
public void AppStarted(string message, int favoriteNumber) => WriteEvent(1, message, favoriteNumber);
[Event(2, Keywords = Keywords.Requests)]
public void RequestStart(int requestId) => WriteEvent(2, requestId);
[Event(3, Keywords = Keywords.Requests)]
public void RequestStop(int requestId) => WriteEvent(3, requestId);
public class Keywords // This is a bitvector
{
public const EventKeywords Startup = (EventKeywords)0x0001;
public const EventKeywords Requests = (EventKeywords)0x0002;
}
}
As palavras-chave devem ser definidas usando uma classe aninhada chamada Keywords e cada palavra-chave individual é definida por um membro digitado public const EventKeywords.
Prática recomendada
Palavras-chave são mais importantes ao distinguir entre eventos de alto volume. Isso permite que um consumidor de eventos eleve a verbosidade para um alto nível, mas gerencie a sobrecarga de desempenho e o tamanho do log habilitando apenas subconjuntos estreitos dos eventos. Os eventos que são disparados mais de 1.000/s são bons candidatos para uma palavra-chave exclusiva.
Tipos de parâmetros compatíveis
EventSource requer que todos os parâmetros de evento possam ser serializados para que ele aceite apenas um conjunto limitado de tipos. Estes são:
- Primitivas: booliano, byte, sbyte, char, short, ushort, int, uint, long, ulong, float, double, IntPtr e UIntPtr, Guid decimal, string, DateTime, DateTimeOffset, TimeSpan
- Enumerações
- Estruturas atribuídas com System.Diagnostics.Tracing.EventDataAttribute. Somente as propriedades de instâncias públicas que possuem tipos serializáveis serão serializadas.
- Tipos anônimos em que todas as propriedades públicas são tipos serializáveis
- Matrizes de tipos serializáveis
- <Nulável> T onde T é um tipo serializável
- KeyValuePair<T, U> onde T e U são tipos serializáveis
- Tipos que implementam IEnumerable<T> para exatamente um tipo T e onde T é um tipo serializável
Resolução de problemas
A classe EventSource foi projetada para que nunca gerasse uma Exceção por padrão. Essa é uma propriedade útil, pois o registro em log geralmente é tratado como opcional e você geralmente não deseja que um erro ao escrever uma mensagem de log faça com que seu aplicativo falhe. No entanto, isso dificulta a localização de qualquer erro em seu EventSource. Aqui estão várias técnicas que podem ajudar a solucionar problemas:
- O construtor do EventSource tem sobrecargas que usam EventSourceSettings. Tente habilitar temporariamente o sinalizador ThrowOnEventWriteErrors.
- A EventSource.ConstructionException propriedade armazena qualquer exceção gerada ao validar os métodos de registro em log de eventos. Isso pode revelar vários erros de criação.
- EventSource registra erros usando a ID do evento 0 e esse evento de erro tem uma cadeia de caracteres que descreve o erro.
- Ao depurar, essa mesma cadeia de caracteres de erro também será registrada usando Debug.WriteLine() e aparecerá na janela de saída de depuração.
- O EventSource gera e depois captura internamente as exceções quando ocorrem erros. Para observar quando essas exceções estão ocorrendo, habilite as exceções de primeira chance em um depurador ou use o rastreamento de eventos com os eventos de Exceção do runtime do .NET habilitados.
Personalizações avançadas
Como configurar OpCodes e Tarefas
O ETW tem conceitos de Tarefas e OpCodes, que são mecanismos adicionais para marcar e filtrar eventos. Você pode associar eventos a tarefas e opcodes específicos usando as propriedades Task e Opcode. Veja um exemplo:
[EventSource(Name = "Samples-EventSourceDemos-Customized")]
public sealed class CustomizedEventSource : EventSource
{
static public CustomizedEventSource Log { get; } = new CustomizedEventSource();
[Event(1, Task = Tasks.Request, Opcode=EventOpcode.Start)]
public void RequestStart(int RequestID, string Url)
{
WriteEvent(1, RequestID, Url);
}
[Event(2, Task = Tasks.Request, Opcode=EventOpcode.Info)]
public void RequestPhase(int RequestID, string PhaseName)
{
WriteEvent(2, RequestID, PhaseName);
}
[Event(3, Keywords = Keywords.Requests,
Task = Tasks.Request, Opcode=EventOpcode.Stop)]
public void RequestStop(int RequestID)
{
WriteEvent(3, RequestID);
}
public class Tasks
{
public const EventTask Request = (EventTask)0x1;
}
}
Você pode criar implicitamente objetos EventTask declarando dois métodos de evento com IDs de eventos subsequentes, seguindo o padrão de nomeação <EventName>Start e <EventName>Stop. Esses eventos devem ser declarados um ao lado do outro na definição de classe e o <método EventName>Start deve vir primeiro.
Autodescrição (tracelogging) versus formatos de evento de manifesto
Esse conceito só importa ao assinar o EventSource do ETW. O ETW tem duas maneiras diferentes de registrar eventos, formato de manifesto e autodescrição (às vezes chamada de tracelogging). Objetos EventSource baseados em manifesto geram e registram um documento XML que representa os eventos definidos na classe após a inicialização. Isso requer que o EventSource reflita sobre si mesmo para gerar o provedor e os metadados de evento. No formato autodescritivo, os metadados de cada evento são transmitidos embutidos com os dados do evento em vez de antecipadamente. A abordagem auto descritiva suporta os métodos mais flexíveis Write que podem enviar eventos arbitrários sem a necessidade de criar um método de log de eventos predefinido. Ele também é um pouco mais rápido na inicialização porque evita uma reflexão ansiosa. No entanto, os metadados extras emitidos com cada evento adicionam uma pequena sobrecarga de desempenho, o que pode não ser desejável ao enviar um alto volume de eventos.
Para usar o formato de evento autodescritivo, construa o EventSource usando o construtor EventSource(String) ou o construtor EventSource(String, EventSourceSettings) ou definindo o sinalizador EtwSelfDescribingEventFormat em EventSourceSettings.
Tipos de EventSource que implementam interfaces
Um tipo EventSource pode implementar uma interface para se integrar perfeitamente em vários sistemas avançados de registro que usam interfaces para definir um alvo comum de registro. Aqui está um exemplo de um possível uso:
public interface IMyLogging
{
void Error(int errorCode, string msg);
void Warning(string msg);
}
[EventSource(Name = "Samples-EventSourceDemos-MyComponentLogging")]
public sealed class MyLoggingEventSource : EventSource, IMyLogging
{
public static MyLoggingEventSource Log { get; } = new MyLoggingEventSource();
[Event(1)]
public void Error(int errorCode, string msg)
{ WriteEvent(1, errorCode, msg); }
[Event(2)]
public void Warning(string msg)
{ WriteEvent(2, msg); }
}
Você deve especificar EventAttribute nos métodos de interface, caso contrário (por motivos de compatibilidade) o método não será tratado como um método de registro em log. A implementação explícita do método de interface não é permitida para evitar colisões de nomenclatura.
Hierarquias de classe EventSource
Na maioria dos casos, você poderá escrever tipos que derivam diretamente da classe EventSource. Às vezes, no entanto, é útil definir a funcionalidade que será compartilhada por vários tipos eventsource derivados, como sobrecargas personalizadas do WriteEvent (consulte otimizar o desempenho para eventos de alto volume abaixo).
Classes base abstratas podem ser usadas desde que não definam palavras-chave, tarefas, opcodes, canais ou eventos. Aqui está um exemplo em que a classe UtilBaseEventSource define uma sobrecarga de WriteEvent otimizada necessária para vários EventSources derivados no mesmo componente. Um desses tipos derivados é ilustrado abaixo como OptimizedEventSource.
public abstract class UtilBaseEventSource : EventSource
{
protected UtilBaseEventSource()
: base()
{ }
protected UtilBaseEventSource(bool throwOnEventWriteErrors)
: base(throwOnEventWriteErrors)
{ }
protected unsafe void WriteEvent(int eventId, int arg1, short arg2, long arg3)
{
if (IsEnabled())
{
EventSource.EventData* descrs = stackalloc EventSource.EventData[2];
descrs[0].DataPointer = (IntPtr)(&arg1);
descrs[0].Size = 4;
descrs[1].DataPointer = (IntPtr)(&arg2);
descrs[1].Size = 2;
descrs[2].DataPointer = (IntPtr)(&arg3);
descrs[2].Size = 8;
WriteEventCore(eventId, 3, descrs);
}
}
}
[EventSource(Name = "OptimizedEventSource")]
public sealed class OptimizedEventSource : UtilBaseEventSource
{
public static OptimizedEventSource Log { get; } = new OptimizedEventSource();
[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); // Calls UtilBaseEventSource.WriteEvent
}
#region Keywords / Tasks /Opcodes / Channels
public static class Keywords
{
public const EventKeywords Kwd1 = (EventKeywords)1;
}
#endregion
}
Otimizando o desempenho para eventos de alto volume
A classe EventSource tem várias sobrecargas para WriteEvent, incluindo uma para o número variável de argumentos. Quando nenhuma das outras sobrecargas corresponde, o método params é chamado. Infelizmente, a sobrecarga de parâmetros é relativamente cara. Em particular:
- Aloca uma matriz para manter os argumentos da variável.
- Converte cada parâmetro em um objeto, o que causa alocações para tipos de valor.
- Atribui esses objetos à matriz.
- Chama a função.
- Descobre o tipo de cada elemento de matriz para determinar como serializá-lo.
Isso provavelmente é 10 a 20 vezes mais caro que tipos especializados. Isso não importa muito para casos de baixo volume, mas para eventos de alto volume pode ser importante. Existem dois casos importantes para garantir que a sobrecarga de parâmetros não seja usada:
- Verifique se os tipos enumerados são convertidos em 'int' para que correspondam a uma das sobrecargas rápidas.
- Crie novas sobrecargas rápidas de WriteEvent para cargas de alto volume.
Aqui está um exemplo para adicionar uma sobrecarga WriteEvent que usa quatro argumentos inteiros
[NonEvent]
public unsafe void WriteEvent(int eventId, int arg1, int arg2,
int arg3, int arg4)
{
EventData* descrs = stackalloc EventProvider.EventData[4];
descrs[0].DataPointer = (IntPtr)(&arg1);
descrs[0].Size = 4;
descrs[1].DataPointer = (IntPtr)(&arg2);
descrs[1].Size = 4;
descrs[2].DataPointer = (IntPtr)(&arg3);
descrs[2].Size = 4;
descrs[3].DataPointer = (IntPtr)(&arg4);
descrs[3].Size = 4;
WriteEventCore(eventId, 4, (IntPtr)descrs);
}