Creating a custom event formatter

patterns & practices Developer Center

Event formatters are used by event sinks to generate the appropriate output format from the payload of events received by the sink. If you need to generate a specific format for your log messages, you can create a new formatter that generates output in exactly the format you require.

You can use a custom formatter in both the in-process and out-of-process scenarios. However, if you want to support IntelliSense for a custom formatter in the configuration file for the Out-of-Process Host application you must do additional work to add this capability.

This topic describes:

  • Creating a custom event formatter
  • Using a custom event formatter in-process
  • Using a custom event formatter out-of-process
  • Defining a configuration element for a custom event formatter
  • Adding IntelliSense support for a custom event formatter

Creating a custom event formatter

To develop a custom event formatter, you create a class that implements the IEventTextFormatter interface. This interface contains a single method called WriteEvent.

public interface IEventTextFormatter
{
  void WriteEvent(EventEntry eventEntry, TextWriter writer);
}

The following example code shows how to create a custom event formatter that is similar to the built-in EventTextFormatter, but prefixes the properties of an event entry with a user-supplied value.

public class PrefixEventTextFormatter : IEventTextFormatter
{
  public PrefixEventTextFormatter(string header, string footer,
    string prefix, string dateTimeFormat)
  {
    this.Header = header;
    this.Footer = footer;
    this.Prefix = prefix;
    this.DateTimeFormat = dateTimeFormat;
  }

  public string Header { get; set; }
  public string Footer { get; set; }
  public string Prefix { get; set; }
  public string DateTimeFormat { get; set; }

  public void WriteEvent(EventEntry eventEntry, TextWriter writer)
  {
    // Write header
    if (!string.IsNullOrWhiteSpace(this.Header)) writer.WriteLine(this.Header);

    // Write properties
    writer.WriteLine("{0}SourceId : {1}", this.Prefix, eventEntry.ProviderId);
    writer.WriteLine("{0}EventId : {1}", this.Prefix, eventEntry.EventId);
    writer.WriteLine("{0}Keywords : {1}", this.Prefix, eventEntry.Schema.Keywords);
    writer.WriteLine("{0}Level : {1}", this.Prefix, eventEntry.Schema.Level);
    writer.WriteLine("{0}Message : {1}", this.Prefix, eventEntry.FormattedMessage);
    writer.WriteLine("{0}Opcode : {1}", this.Prefix, eventEntry.Schema.Opcode);
    writer.WriteLine("{0}Task : {1} {2}", this.Prefix, eventEntry.Schema.Task, eventEntry.Schema.TaskName);
    writer.WriteLine("{0}Version : {1}", this.Prefix, eventEntry.Schema.Version);
    writer.WriteLine("{0}Payload :{1}", this.Prefix, FormatPayload(eventEntry));
    writer.WriteLine("{0}Timestamp : {1}", this.Prefix, eventEntry.GetFormattedTimestamp(this.DateTimeFormat));

    // Write footer
    if (!string.IsNullOrWhiteSpace(this.Footer)) writer.WriteLine(this.Footer);
    writer.WriteLine();
  }
 
  private static string FormatPayload(EventEntry entry)
  {
    var eventSchema = entry.Schema;
    var sb = new StringBuilder();
    for (int i = 0; i < entry.Payload.Count; i++)
    {
      // Any errors will be handled in the sink.
      sb.AppendFormat(" [{0} : {1}]", eventSchema.Payload[i], entry.Payload[i]);
    }
    return sb.ToString();
  }
}

You must compile the event formatter class into a DLL and reference it in the in-process scenario, or copy it into the same folder as the configuration file in the out-of-process scenario.

You can use the CustomFormatterUnhandledFault method in the SemanticLoggingEventSource class to log any unhandled exceptions in your custom formatter class, but you should always avoid throwing an exception in the WriteEvent method body.

Using a custom event formatter in-process

Using event formatters when you are using the Semantic Logging Application Block in-process is a simple case of instantiating and configuring in code the formatter you want to use, and then passing it as a parameter to the method that initializes the sink you are using. You do exactly the same with your custom formatters.

The following example code shows how to create and configure an instance of the PrefixEventTextFormatter formatter class and pass it to the LogToConsole extension method that subscribes a ConsoleSink instance to an event listener.

ObservableEventListener listener = new ObservableEventListener();

var formatter = new PrefixEventTextFormatter("-----------", null, "# ", "d");
listener.LogToConsole(formatter);

listener.EnableEvents(MyCompanyEventSource.Log, EventLevel.LogAlways, Keywords.All);

Using a custom event formatter out-of-process

When you use a custom formatter in the out-of-process scenario, you must compile it into a DLL and then instantiate it by adding entries to the configuration file of the Out-of-Process host application. The compiled DLL that contains your custom formatter implementation must be copied to the folder from where you will run the Out-of-Process Host application. The application scans this folder for extension classes when it starts.

To add the custom formatter to the configuration file, you use the customEventTextFormatter element that is part of the standard configuration schema for the Out-of-Process host. The parameters to pass to the formatter are specified using nested parameter elements. You must ensure that the order and type of the parameter elements in the configuration file match the order and type of the constructor parameters in the custom formatter class.

The following excerpt from an XML configuration file shows how to use the custom formatter class named PrefixEventTextFormatter with one of the built-in sinks from the Semantic Logging Application Block.

<sinks>

  <consoleSink name="ConsoleSink">
    <sources>
      <eventSource name="MyCompany" level="LogAlways" />
    </sources>

    <customEventTextFormatter
      type="MyCustomNamespace.PrefixEventTextFormatter, MyCustomNamespace">
      <parameters>
        <parameter name="header" type="System.String"
          value="==============================================" />
        <parameter name="footer" type="System.String"
          value="==============================================" />
        <parameter name="prefix" type="System.String" value="# " />
        <parameter name="dateTimeFormat" type="System.String" value="O" />
      </parameters>
    </customEventTextFormatter>
  </consoleSink>

</sinks>

Defining a configuration element for a custom event formatter

You have seen how you can use a custom event formatter out-of-process by adding the customEventTextFormatter element to the configuration file of the Out-of-Process host application. However, you can improve the administrative experience for adding a custom formatter by creating a specific element for it that can be used in the same way as the elements for the event formatters provided with the Semantic Logging Application Block.

For example, the following excerpt from a configuration file shows how you could use a custom element named prefixEventTextFormatter with the Out-of-Process Host application.

<prefixEventTextFormatter
   header="==============================================================="
   footer="==============================================================="
   prefix="# "
   dateTimeFormat="O"/>

To use a custom XML element in this way you must create a class that implements the IFormatterElement interface, and that supports reading your custom configuration data from the XML file. The following code shows an example.

public class PrefixEventTextFormatterElement : IFormatterElement
{
  private readonly XName formatterName = 
    XName.Get("prefixEventTextFormatter", "urn:sample.etw.customformatter");

  public bool CanCreateFormatter(System.Xml.Linq.XElement element)
  {
    return this.GetFormatterElement(element) != null;
  }

  public IEventTextFormatter CreateFormatter(System.Xml.Linq.XElement element)
  {
    var formatter = this.GetFormatterElement(element);

    var header = (string)formatter.Attribute("header");
    var footer = (string)formatter.Attribute("footer");
    var prefix = (string)formatter.Attribute("prefix");
    var datetimeFormat = (string)formatter.Attribute("dateTimeFormat");

    return new PrefixEventTextFormatter(header,footer, prefix, datetimeFormat);
  }

  private XElement GetFormatterElement(XElement element)
  {
    return element.Element(this.formatterName);
  }
}

The class must then be compiled into a DLL and placed in the same folder as the XML configuration file for the Out-of-Process Host application.

Adding IntelliSense support for a custom event formatter

The formatters provided with the Semantic Logging Application Block have built-in IntelliSense support for configuring the Out-of-Process host. However, your custom formatters do not support this by default.

If you have defined a custom XML element and attributes for use when configuring a custom formatter, you can create a schema that enables IntelliSense support in the Visual Studio XML editor. When you do this, the XML configuration file will look like the following example.

<?xml version="1.0"?>
<configuration
 xmlns="https://schemas.microsoft.com/practices/2013/entlib/semanticlogging/etw"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="urn:sample.etw.customformatter
 PrefixEventTextFormatterElement.xsd
 https://schemas.microsoft.com/practices/2013/entlib/semanticlogging/etw
 SemanticLogging-svc.xsd">

<traceEventService />

<sinks>
  <consoleSink name="Consolesink">
    <sources>
      <eventSource name="MyCompany" level="LogAlways" />
    </sources>

    <prefixEventTextFormatter xmlns="urn:sample.etw.customformatter"
     header="====================================================="
     footer="====================================================="
     prefix="# "
     dateTimeFormat="O"/>
  </consoleSink>
  ...

Note

Notice how the prefixEventTextFormatter element has a custom XML namespace and the configuration file uses a schemaLocation attribute to specify the location of the schema file.

The schema you create must specify the XML structure for the custom formatter element, and apply any restrictions or rules for the values of the attributes and any child elements you use. In this example, the namespace urn:demo.etw.customformatter is used to define the prefixEventTextFormatter element and its content.

<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="PrefixEventTextFormatterElement"
    targetNamespace="urn:sample.etw.customformatter"
    xmlns="urn:sample.etw.customformatter"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"           
    elementFormDefault="qualified"
    attributeFormDefault="unqualified">

  <xs:element name="prefixEventTextFormatter">
    <xs:complexType>
      <xs:attribute name="header" type="xs:string" use="required" />
      <xs:attribute name="footer" type="xs:string" use="required" />
      <xs:attribute name="prefix" type="xs:string" use="required" />
      <xs:attribute name="dateTimeFormat" type="xs:string" use="required" />
    </xs:complexType>
  </xs:element>
</xs:schema>

You must copy the custom XML schema file to the same folder where you store the XML configuration file for the Out-of-Process Host application. This folder should already contain the DLL that implements your custom formatter and your custom EventTextFormatterElement type.

Next Topic | Previous Topic | Home | Community