Skip to content

Event formatters

FantasticFiasco edited this page Sep 4, 2021 · 7 revisions

The event formatter is responsible for turning a single log event into a textual representation. It can serialize the log event into JSON, XML or anything else that matches the expectations of the receiving log server.

The sink comes pre-loaded with five JSON based event formatters where NormalRenderedTextFormatter is the default. You can decide to configure your sink to use any of these five, or write your own by implementing ITextFormatter.

/// <summary>
/// Formats log events in a textual representation.
/// </summary>
public interface ITextFormatter
{
  /// <summary>
  /// Format the log event into the output.
  /// </summary>
  /// <param name="logEvent">
  /// The event to format.
  /// </param>
  /// <param name="output">
  /// The output.
  /// </param>
  void Format(LogEvent logEvent, TextWriter output);
}

Formatter types

Event formatter 1 (default) - NormalRenderedTextFormatter

The log event is normally formatted and the message template is rendered into a message. This is the most verbose formatting type and its network load is higher than the other options.

{
  "Timestamp": "2016-11-03T00:09:11.4899425+01:00",
  "Level": "Information",
  "MessageTemplate": "Logging {@Heartbeat} from {Computer}",
  "RenderedMessage": "Logging { UserName: \"Mike\", UserDomainName: \"Home\" } from \"Workstation\"",
  "Properties": {
    "Heartbeat": {
      "UserName": "Mike",
      "UserDomainName": "Home"
    },
    "Computer": "Workstation"
  }
}

Event formatter 2 - NormalTextFormatter

The log event is normally formatted and its data normalized. The lack of a rendered message means improved network load compared to NormalRenderedTextFormatter. Often this formatting type is complemented with a log server that is capable of rendering the messages of the incoming log events.

{
  "Timestamp": "2016-11-03T00:09:11.4899425+01:00",
  "Level": "Information",
  "MessageTemplate": "Logging {@Heartbeat} from {Computer}",
  "Properties": {
    "Heartbeat": {
      "UserName": "Mike",
      "UserDomainName": "Home"
    },
    "Computer": "Workstation"
  }
}

Event formatter 3 - CompactRenderedTextFormatter

The log event is formatted with minimizing size as a priority but still render the message template into a message. This formatting type reduces the network load and should be used in situations where bandwidth is of importance.

The compact formatter adheres to the following rules:

  • Built-in field names are short and prefixed with an @
  • The Properties property is flattened
  • The Information level is omitted since it is considered to be the default
{
  "@t": "2016-11-03T00:09:11.4899425+01:00",
  "@mt": "Logging {@Heartbeat} from {Computer}",
  "@m":"Logging { UserName: \"Mike\", UserDomainName: \"Home\" } from \"Workstation\"",
  "Heartbeat": {
    "UserName": "Mike",
    "UserDomainName": "Home"
  },
  "Computer": "Workstation"
}

Event formatter 4 - CompactTextFormatter

The log event is formatted with minimizing size as a priority and its data is normalized. The lack of a rendered message means even smaller network load compared to CompactRenderedTextFormatter and should be used in situations where bandwidth is of importance. Often this formatting type is complemented with a log server that is capable of rendering the messages of the incoming log events.

The compact formatter adheres to the following rules:

  • Built-in field names are short and prefixed with an @
  • The Properties property is flattened
  • The Information level is omitted since it is considered to be the default
{
  "@t": "2016-11-03T00:09:11.4899425+01:00",
  "@mt": "Logging {@Heartbeat} from {Computer}",
  "Heartbeat": {
    "UserName": "Mike",
    "UserDomainName": "Home"
  },
  "Computer": "Workstation"
}

Event formatter 5 - NamespacedTextFormatter

The formatter is configured with a custom namespace, in this example Foo.Bar, and the message properties are serialized into this namespace. This formatting type is suited for a micro-service architecture where log events are sent to the Elastic Stack. The namespace reduces the risk of two services logging properties with identical names but with different types, which the Elastic Stack doesn't support.

Enriched properties are serialized into Properties, not into the namespace, and the motivation behind this is that enriched properties often are the result of shared enrichers.

The message is rendered by default, but can be configured to be omitted.

{
  "Timestamp": "2016-11-03T00:09:11.4899425+01:00",
  "Level": "Information",
  "MessageTemplate": "Logging {@Heartbeat} from {Computer}",
  "RenderedMessage": "Logging { UserName: \"Mike\", UserDomainName: \"Home\" } from \"Workstation\"",
  "Properties": {
    "ThreadId": 1234,
    "Foo": {
      "Bar": {
        "Heartbeat": {
          "UserName": "Mike",
          "UserDomainName": "Home"
        },
        "Computer": "Workstation"
      }
    }
  }
}

Custom formatter

You can always implement your own custom formatter assuming none of the above are to your satisfaction. Let's implement a formatter that will produce the following JSON object.

{
  "Timestamp": "2016-11-03T00:09:11.4899425+01:00"
}

As stated in the beginning of the document, we need to implement the ITextFormatter interface. Let's also add some error handling given anything fails during serialization. Our new formatter would look something like this.

public class MyTextFormatter : ITextFormatter
{
  public void Format(LogEvent logEvent, TextWriter output)
  {
    try
    {
      var buffer = new StringWriter();
      FormatContent(logEvent, buffer);

      // If formatting was successful, write to output
      output.WriteLine(buffer.ToString());
    }
    catch (Exception e)
    {
      LogNonFormattableEvent(logEvent, e);
    }
  }

  private void FormatContent(LogEvent logEvent, TextWriter output)
  {
    if (logEvent == null) throw new ArgumentNullException(nameof(logEvent));
    if (output == null) throw new ArgumentNullException(nameof(output));

    output.Write("{\"Timestamp\":\"");
    output.Write(logEvent.Timestamp.ToString("o"));
    output.Write('}');
  }

  private static void LogNonFormattableEvent(LogEvent logEvent, Exception e)
  {
    SelfLog.WriteLine(
      "Event at {0} with message template {1} could not be formatted into JSON and will be dropped: {2}",
      logEvent.Timestamp.ToString("o"),
      logEvent.MessageTemplate.Text,
      e);
  }
}