Developing event sources using the .NET EventSource class

patterns & practices Developer Center

Before you can write log messages using an EventSource class, you must define what log messages you will write. This is what semantic logging means. You can specify the log messages you will write by extending the EventSource class from the System.Diagnostics.Tracing namespace in the .NET 4.5 framework. In the terms used by ETW, an EventSource implementation represents an “Event Provider.”

Each event type in your application is represented by a method in your custom event source implementation. These event methods take parameters that define the payload of the event, and are decorated with attributes that provide additional metadata such as the event ID or verbosity level. Behind the scenes, the event source infrastructure extracts information about your application’s events to build event schemas and a manifest by using reflection on your event source classes.

This topic provides a brief introduction for creating custom event sources. It includes the following sections:

  • Overview of custom event source classes
  • Using the WriteEvent and IsEnabled methods
  • Specifying the event and its payload
  • Specifying the log level
  • Using keywords
  • Using op-codes and tasks
  • Managing sensitive data
  • Versioning and updating an event source
  • Checking an event source for errors

For more information about creating custom event sources, see EventSource Class on MSDN.

Overview of custom event source classes

The following code sample shows an example EventSource implementation. This example includes the optional nested Keywords and Tasks classes (described later) that enable you to define additional information for your log messages.

[EventSource(Name = "MyCompany")]
public class MyCompanyEventSource : EventSource
{
    public 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 class Tasks
    {
      public const EventTask Page = (EventTask)1;
      public const EventTask DBQuery = (EventTask)2;
    }

    private static MyCompanyEventSource _log = new MyCompanyEventSource();
    private MyCompanyEventSource() { }
    public static MyCompanyEventSource Log { get { return _log; } }

    [Event(1, Message = "Application Failure: {0}", 
    Level = EventLevel.Critical, Keywords = Keywords.Diagnostic)]
    internal void Failure(string message)
    {
      this.WriteEvent(1, message);
    }

    [Event(2, Message = "Starting up.", Keywords = Keywords.Perf,
    Level = EventLevel.Informational)]
    internal void Startup()
    {
      this.WriteEvent(2);
    }

    [Event(3, Message = "loading page {1} activityID={0}",
    Opcode = EventOpcode.Start,
    Task = Tasks.Page, Keywords = Keywords.Page,
    Level = EventLevel.Informational)]
    internal void PageStart(int ID, string url)
    {
      if (this.IsEnabled()) this.WriteEvent(3, ID, url);
    }
    ...
}

The example class MyCompanyEventSource show above, which extends the EventSource class, contains definitions for all of the events that you want to be able to log—each one defined by its own method. Attributes on each event method define the metadata, such as the message, level, and keywords of the event. Notice that the Event attribute includes the event identifier as its first parameter. Every other parameter in this attribute is optional; the only required parameter is the event identifier.

You can use the EventSource attribute on the class itself to provide a more user-friendly name for this event source, which will be used when you save log messages. If you omit the EventSource attribute, the class name is used as the name of the event source.

Note that there is a recommended naming convention*for event source classes. The name should start with your company name and, if you expect to have more than one event source for your company, the name should include categories separated by a hyphen. Microsoft follows this convention; for example, there is an event source called *Microsoft-Windows-DotNetRuntime. You should carefully consider your own naming strategy because, if you change it in the future, users consuming the events will no longer receive them.

Note

Each event source you implement should have a unique name, although you might choose to reuse an event source in multiple applications. In this case, if you use the same name for the event source, the Semantic Logging Out-of-Process Host will collect and process all of the events.

Event sources are shared instances. The example MyCompanyEventSource class defines the constructor as private and includes a static member named Log that provides access to a shared instance of the class. The following code sample shows an alternative approach for defining the access to the singleton.

private static readonly Lazy<MyCompanyEventSource> Instance =
  new Lazy<MyCompanyEventSource>(() => new MyCompanyEventSource());

private MyCompanyEventSource() { }

public static MyCompanyEventSource Log { get { return Instance.Value; } }

Note

Custom event source files can become quite large. You should consider using partial classes to make them more manageable.

Using the WriteEvent and IsEnabled methods

Each event is defined using a method that wraps a call to the WriteEvent method in the EventSource class. The first parameter of the WriteEvent method is an event identifier that must be unique within the event source. Different overloaded versions of this method, which must use different event identifiers, enable you to write additional information to the log.

Note

If you want to be able to correlate events raised by asynchronous tasks and new threads spawned by the application, you must publish transfer events by using the WriteEventWithRelatedActivityId method in your event source instead of the WriteEvent method. For more information, see Correlating events.

In the example MyCompanyEventSource class, the methods such as PageStart include a call to the IsEnabled method in the base class to determine whether to write a log message. The IsEnabled method helps to improve performance if you are using the costly overload of the WriteEvent method that includes an object array parameter.

You can call the IsEnabled method in helper methods within your event source class if required. For example, methods that are not defined as events, but that perform some pre-processing, transforming, or formatting of data before calling an event method, may not need to execute if the event is not enabled.

An overload of the IsEnabled method is available that can check whether the event source is enabled for particular keywords and levels. However, with this overload, it is important to make sure that the parameters to the IsEnabled method match the keywords and levels in the EventAttribute attribute.

For more information about the EventSource class and the WriteEvent and IsEnabled methods, see EventSource Class on MSDN.

Specifying the event and its payload

The event methods in your event source class enable you to provide discrete pieces of information about the event that will be included in the payload. For example, the PageStart event method in the MyCompanyEventSource example class enables you to include a URL in the payload. However, payload items do not necessarily have to be strings. A major advantage of using ETW-compatible events is that the payload items are typed values.

Some sinks will store payload items individually; for example, the SQL Database sink and the Azure Table Storage sink in the Semantic Logging Application Block store each payload item in a separate column.

Developers typically create methods in their custom event source classes with the payload items and the bare minimum relevant metadata if there is any, and get back to coding the business logic. At a later point, someone on the team looks at the event source class as a whole and starts assigning the appropriate metadata and payload to make the output of the entries consistent with each other and to expose the necessary information.

Note

It is vital to involve administrators and operators in the planning of events and payloads in order to expose the information required to manage the application.

An event message is a human readable version of the payload information, and you control the format using the Message parameter of the Event attribute. For example, the Startup method in the MyCompanyEventSource example class always writes the string “Starting up” to the log when invoked. The PageStart method (repeated below) writes a message to the log, substituting the values of the ID and url parameters for the two placeholders in the Message attribute string.

[Event(3, Message = "loading page {1} activityID={0}",
       Opcode = EventOpcode.Start,
       Task = Tasks.Page, Keywords = Keywords.Page,
       Level = EventLevel.Informational)]
  internal void PageStart(int ID, string url)
  {
    if (this.IsEnabled()) this.WriteEvent(3, ID, url);
  }

Although the event source class does a lot of the heavy lifting for you, you still need to do your part. For example, you must also pass these parameters on to the call to the WriteEvent method, and ensure that you pass them in the same order as you define them in the method signature.

Specifying the log level

You can use the Level parameter of the Event attribute to specify the severity level of the message. The EventLevel enumeration determines the available log levels: Verbose (5), Informational (4), Warning (3), Error (2), Critical (1), and LogAlways (0). Informational is the default logging level when not specified. When you enable an event source in your application, you can specify a log level, and the event source will log all log messages with same or lower log level. For example, if you enable an event source with the warning log level, all log methods with a level parameter value of Warning, Error, Critical, and LogAlways will be able to write log messages.

You should use log levels with values lower than Informational (such as Warning, Error and Critical) for relatively rare warnings or errors. When in doubt, use the default of Informational level. Use the Verbose level for events that can happen more than 1,000 times per second. Typically, users filter by severity level first and then, if necessary, refine the filter by using keywords.

Using keywords

The MyCompanyEventSource example class includes a Keywords parameter for the Event attribute for many of the log methods. You can use the optional keywords capability to define different groups of events so that, when you enable an event source, you can specify which groups of events to enable. Only events whose Keywords parameter matches one of the specified groups will be able to write to the log. You can also use the keywords to filter and analyze the events in your log.

If you decide to use keywords, you must define the keywords you will use in a nested class called Keywords, as shown in the example. Although Keywords looks like an enumeration, it is a static class with constants of type System.Diagnostics.Tracing.EventKeywords. Each keyword value is a 64-bit integer, which is treated as a bit array (a few of the highest bits are reserved, but it still allows you to define a sufficient number of different keywords). You must ensure you assign powers of two as the value for each constant. This is the class used in the MyCompanyEventSource example.

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

You can associate a log method with multiple keywords, as shown in the following example where the Failure message is associated with both the Diagnostic and Perf keywords.

[Event(1, Message = "Application Failure: {0}", Level = EventLevel.Critical,
       Keywords = Keywords.Diagnostic|Keywords.Perf)]
internal void Failure(string message)
{
  if (this.IsEnabled()) this.WriteEvent(1, message); 
}

The following are some recommendations for using keywords in your event source classes:

  • Events that you expect to fire less than 100 times per second do not need special treatment. You should use a default keyword for these events.
  • Events that you expect to fire more than 1000 times per second should have a keyword. This will enable you to turn them off if you don’t need them.
  • It’s up to you to decide whether events that typically fire at a frequency between these two values should have a keyword.
  • Users will typically want to switch on a specific keyword when they enable an event source, or enable all keywords.
  • Even when the frequency of events is not high, you might want to use keywords to be able to analyze the log, and filter messages at a later stage.

Using op-codes and tasks

You can use the Opcodes and Tasks parameters of the Event attribute to add additional information to the message that the event source logs. Op-codes and tasks are defined using nested classes named Opcodes and Tasks, in a similar way to that used to define keywords. However, op-codes and tasks do not need to be assigned values that are powers of two. They can use sequential integer values.

If you choose to define custom op-codes, you should assign integer values of 11 or above. If you define custom op-codes with values less than 11, they will clash with the op-codes defined in the EventOpcode enumeration and messages that use these op-codes will not be delivered.

The events generate by the event source will contain just the numeric task and op-code identifiers, and not the text representations. The developers creating the event source class, and the administrators and operators using the logs, must agree on the definitions of the tasks and op-codes used in the application.

For example, the MyCompanyEventSource event source class shown earlier includes two tasks named Page and DBQuery (the definition of these two tasks is repeated below). Although these constants have meaningful names, the tasks will appear in the logs as task IDs 1 and 2 respectively.

public class Tasks
{
  public const EventTask Page = (EventTask)1;
  public const EventTask DBQuery = (EventTask)2;
}

Managing sensitive data

You should make sure that you do not write sensitive data to the logs where it may be available to someone who should not have access to that data. One possible approach is to scrub sensitive data from the event within the event source class itself. For example, if you had a requirement to write details of a connection string in a log event, you could remove the password using the following technique.

[Event(250, Level = EventLevel.Informational,
 Keywords = Keywords.DataAccess, Task = Tasks.Initialize)]
public void ExpenseRepositoryInitialized(string connectionString)
{
  if (this.IsEnabled(EventLevel.Informational, Keywords.DataAccess))
  {
    // Remove sensitive data
    var csb = new SqlConnectionStringBuilder(connectionString)
      { Password = string.Empty };
    this.WriteEvent(250, csb.ConnectionString);
  }
}

Versioning and updating an event source

The methods in your custom event source class will be called from multiple places in your application, and possibly from multiple applications if you share the same event source class. For example, you may have multiple versions of your event source class in multiple applications that are using the out-of-process approach for collecting events.

When you modify your event source class, you should take care that any changes you make do not have unexpected consequences for your existing applications. If you do need to modify your event source, you should restrict the changes to adding methods to support new log messages, and adding overloads of existing methods (which must have a new event ID). You should not delete or change the signature of existing methods after you have released your event source.

By specifying a new version number for your event source, you allow the Semantic Application block to detect and use the latest version. This avoids the requirement to synchronize changes to the EventSource class across all of the applications that share it.

Checking an event source for errors

When you create a custom event source implementation for your application, you must follow a number of conventions if the event methods are to work as expected. For example, you must match the identifier in the Event attribute to the identifier passed to the WriteEvent method. Although the event source class does perform some basic checks, such as that the supplied event identifier is valid, these checks are not exhaustive.

The design of the event sources infrastructure is such that event sources never raise exceptions. They will silently fail to work if there is an error, or if the class or the methods within it are not valid. This can make finding and resolving errors more difficult. To help you verify that your event source class follows the conventions, and is valid, you can use the Event Source Analyzer that is available as part of the Semantic Logging Application Block.

The Event Source Analyzer checks your custom event source class by using the following techniques:

  • It checks for errors when it enables an event listener using an EventSource instance.
  • It verifies that it can request event schemas from the EventSource without errors.
  • It verifies that it can invoke each event method in the EventSource class without errors.
  • It verifies that each event method supplies the correct event ID, and that all the payload parameters are passed in the correct order.

To obtain the event source analyzer, use the NuGet Package Manager in Visual Studio to install the package named Semantic Logging Application Block - Event Source Analyzer (EnterpriseLibrary.SemanticLogging.EventSourceAnalyzer).

Using the Event Source Analyzer

The EventSourceAnalyzer class includes the Inspect method and a static InspectAll method. You can use the Event Source Analyzer in a Unit Test as part of your regular development and testing regime, or invoke the methods directly in an application created to test your event source class. The Inspect and the InspectAll methods throw an EventSourceAnalyzerException should any of the checks fail. The exception contains details of the cause of the failure.

The simplest usage of the Event Source Analyzer is to perform all of the checks on your event source class by using the InspectAll method. The following code shows an example of using this method.

[TestMethod]
public void ShouldValidateEventSource()
{
  EventSourceAnalyzer.InspectAll(SemanticLoggingEventSource.Log);
}

As an alternative, if you want to specify which tests will be executed, you can create an instance of the EventSourceAnalyzer class and call the Inspect method. Before you call this method, you can set some Boolean properties, as described in the following table.

Property

Description

ExcludeEventListenerEmulation

Gets or sets a value indicating whether to exclude using the internal System.Diagnostics.Tracing.EventListener instance to emulate sending events. The analyzer performs a 'probing' execution using an internal EventListener instance to emulate logging using the inspected EventSource instance. By excluding this analysis, no ETW events are sent. This is useful if you are executing the analysis from a running application where ETW events may be monitored.

ExcludeWriteEventTypeMapping

Gets or sets a value indicating whether to exclude an exact type mapping between the event arguments and EventSource.WriteEvent argument.

ExcludeWriteEventTypeOrder

Gets or sets a value indicating whether to exclude the type order analysis between the event arguments and the EventSource.WriteEvent arguments order. This process is costly and may be excluded for large event sources containing events with many arguments. However, it is recommended to leave this option off (the default setting) to ensure that all EventSource.WriteEvent arguments are correctly mapped to the event parameters.

Details of the individual event source validation checks

The following table lists the specific checks that the Inspect method of the EventSourceAnalyzer class performs.

Test

Description

Singleton event source

Checks that the EventSource instance is a singleton.

Manifest generation

Checks that a manifest can be generated from the EventSource class

Message format

Checks that the Message parameter of the Event attribute does not contain any invalid characters.

Event id mismatch

Checks that the event id in the Event attribute matches the event id passed to the WriteEvent method.

No event in the event source

Checks that the EventSource class contains at least one event method.

Duplicate events

Checks that event ids are not duplicated anywhere in the EventSource class.

Missing Event attributes

Checks that event methods in the EventSource class are decorated with the Event attribute.

Missing call to WriteEvent

Checks that all event methods invoke the WriteEvent method.

Mismatching keywords

Checks that the keywords used in the Event attribute match the keywords passed to the IsEnabled method, if it is invoked.

Undefined opcode

Checks that op-codes used in the Event attribute have been defined.

Same type arguments order mismatch

Checks that if the event method has a sequence of parameters of the same type, that they are passed in the same order to the WriteEvent method.

Different type arguments order mismatch

Checks that, if the event method has a sequence of parameters of different types, they are passed in the same order to the WriteEvent method.

Enum types generate an invalid manifest

The use of certain enum types results in an invalid manifest being generated.

Next Topic | Previous Topic | Home | Community