Skip to content

Controlling Logging Events through Attributes

Jon Wagner edited this page Mar 21, 2015 · 3 revisions

Controlling Logging Events Through Attributes

Although you can define a logging interface as just any interface, you will probably want to customize the logging details a little bit. These are done through attributes on your class and methods.

Back to our example:

[EventSourceImplementation(Name="Calculator")]
public interface ICalculatorEventSource
{
	[Event(1, Message="Clear", Level=EventLevel.Informational)]
	void Clear();

	[Event(2, Message="Add {0} {1}", Level=EventLevel.Informational)]
	int Add(int x, int y);

	[Event(3, Message="Multiply {0} {1}", Level=EventLevel.Informational)]
	int Multiply(int x, int y);
}

// somewhere else in our code...
public static ICalculatorEventSource Log = EventSourceImplementer.GetEventSourceAs<ICalculatorEventSource>();

EventSourceImplementation Attribute

The EventSourceImplementation Attribute controls how the logging class is created. It is analogous to the EventSource Attribute in .NET, with a few extras:

  • Name - overrides the name of the event provider. By default it is the name of the class without the namespace.
  • Guid - overrides the GUID of the event provider. By default it is generated from the name. Overriding the GUID is not recommended.
  • Keywords - specifies the class to use for the EventKeywords implementation. By default, ESP will look in the class or auto-generate the Keywords.
  • Opcodes - specifies the class to use for the EventOpcodes implementation. By default, ESP will look in the class.
  • Tasks - specified the class to use for the EventTasks implementation. By default, ESP will look in the class.
  • ImplementComplementMethods - by default, ESP will generate _Completed and _Faulted methods on interfaces so that TracingProxies can log start/stop events. By default, ESP will not generate these methods for classes derived from EventSource. This can be overridden by setting the ImplementComplementMethods property.

If you are using ESP to complete the implementation of an EventSource class, you can use the built-in EventSource attribute, but if you are using interface implementation, you must use the EventSourceImplementation Attribute.

One advantage to using ESP for your event source is that it can share the enumeration classes across interfaces. For example, if you want to tag your entries by type of operation, you can define the Keywords flags yourself:

[EventSourceImplementation(Keywords=typeof(BeerKeywords))]
public interface IBeerRepository
{
	[Event(Keywords=BeerKeywords.Beer)]
	void BeerAdded(Beer beer);

	[Event(Keywords=BeerKeywords.Beer)]
	void BeerRemoved(Beer beer);

	[Event(Keywords=BeerKeywords.Glass)]
	void GlassAdded(Glass glass);

	[Event(Keywords=BeerKeywords.Glass)]
	void GlassRemoved(Glass glass);
}

public static class BeerKeywords
{
	public const EventKeywords Beer = (EventKeywords)1 << 0;
	public const EventKeywords Glass = (EventKeywords)1 << 1;
}

Here we have defined keywords by type of object (beer or glass). We can now turn on logging for Beer operations separately from Glass operations. If we have other logging operations that semantically use the same keywords, we can reuse the BeerKeywords definition. The same pattern works for Opcodes and Tasks. Opcodes and Tasks are other aspects of events:

  • Task - an overall operation that is to be performed, such as "InventoryLookup"
  • Opcode - generally represents the time dimension or a single operation within the task, such as "Starting", "FetchedData", "Formatted", "Stopping"
  • Keywords - generally represents a functional dimension of the task, such as "Database", "Formatting", "Network"

You can also change the implementation attribute at runtime. For example, if you get an interface from someone else, you can change it this way:

EventSourceImplementation.For<IFrameworkSomething>(new EventSourceImplementation(Name="Stuff")]

Event Attribute

The Event Attribute is part of .NET 4.5, it is applied to methods in the Event Source class or interface. It defines the Event Id, Message, and other elements of the event.

  • EventId - a number representing the Event. If omitted, ESP will choose an ID greater than the highest EventId used in the interface or class.
  • Keywords - the keywords for the event.
  • Level - the level of severity of the event.
  • Message - a readable string for the event, with standard .NET substitution operators (e.g. {1} in the string)
  • Opcode - the opcode for the event.
  • Task - the task for the event.
  • Version - the version number for the event. Increment this value if you change the parameters of the event and want to merge the events with older versions of the data.

EventAttributeProvider

You may want to keep your interface clean and not add attributes directly to the interface. If so, you can implement an EventAttributeProvider and register that for your interface:

class MyEventAttributeProvider : EventAttributeProvider
{
	public override EventAttribute GetEventAttribute(InvocationContext context, int nextEventId)
	{
		// look up the attribute data from XML or configuration perhaps?
		EventAttribute eventAttribute = new EventAttribute(nextEventID);

		return new EventAttribute(nextEventId)
		{
			Level = GetEventLevelForContext(context, null),
			Message = GetEventMessage(context)
		};
	}
}

Changing the Default Logging Levels

If you do not specify an EventLevel, ESP (and ETW) will log your events at the Informational level. You can change that by adding an EventAttribute to your method or by overriding the EventAttributeProvider for your interface:

Changing the logging level with a class attribute:

[EventSourceImplementation(Level=EventLevel.Critical)]
public interface IBeerRepository
{
}

Changing the logging level at a method level:

public interface IBeerRepository
{
	[Event(1, Level=EventLevel.Critical)]
	void BeerAdded(Beer beer);
}

Changing the logging level by overriding the EventAttributeProvider:

public interface IBeerRepository
{
	void BeerAdded(Beer beer);
}

// we really want to know when we touch the beer
EventSourceImplementation.RegisterProvider<IBeerRepository>(new EventAttributeProvider(EventLevel.Critical));