Configuring and using the Trace subsystem in .Net

So, why?

Because it’s super quick, built into the .net framework already and it’s thread safe and production ready.

So, how?

There are two parts to production ready tracing:

  1. Configuring a Trace Source
  2. Writing the code to use it

1. Configuring a Trace Source

[sourcecode language=”xml”]

<system.diagnostics>
<sources>
<source name="UltimaSource"
switchName="SourceSwitch"
switchType="System.Diagnostics.SourceSwitch" >
<listeners>
<add name="MyTraceFile" />
<add name="ColouredConsole" />
<remove name ="Default" />
</listeners>
</source>
</sources>
<switches>
<add name="SourceSwitch" value="Information" />
</switches>
<sharedListeners>
<add name="MyTraceFile" type="System.Diagnostics.TextWriterTraceListener" initializeData="Trace.log" />
<add name="ColouredConsole" type="Ultima.Infrastructure.Logging.ColouredConsoleTraceLogger, Ultima.Infrastructure, Version=1.0.0.0, Culture=neutral" />
</sharedListeners>
</system.diagnostics>

[/sourcecode]

The code above configures a trace source called UltimaSource, which has two listeners wired up; a Coloured Console Listener (home brewed) and a Text Writer Trace Listener. A switch called SourceSwitch, of type  System.Diagnostics.SourceSwitch is wired up, which is used to control the level of output we get from the listeners. The level is controlled by changing the Value of the relevant switch in the switches element.

You should take note that there is no Trace element in the config above. It would look like thus:

[sourcecode language=”xml”]

<trace autoflush="true">
<listeners>
<add name="MyTraceFile" />
<add name="ColouredConsole" />
</listeners>
</trace>

[/sourcecode]

There is now way that I am aware of that you can control the level of output when you use a simple Trace configuration like this without a Trace Source. This is pretty nice however for your local machine config whilst coding. A simple config transform could remove it as required.

2. Writing the code to use it

In order to use a configured Trace Source, you need to fetch it by name. I have wrapped and abstracted some of the noise in the simple class below:

[sourcecode language=”csharp”]

public class UltimaTrace
{
private static readonly TraceSource Log = new TraceSource("UltimaSource");

public static void TraceInformation(string message)
{
Log.TraceInformation(message);
}

public static void TraceInformation(string format, params object[] message)
{
Log.TraceInformation(format, message);
}

public static void TraceError(string error)
{
Log.TraceEvent(TraceEventType.Error, 0, error);
}

public static void TraceError(string format, params object[] error)
{
Log.TraceEvent(TraceEventType.Error, 0, format, error);
}

public static void TraceException(params object[] data)
{
Log.TraceData(TraceEventType.Error, 0, data);
}
}

[/sourcecode]

Note the TraceEvent and TraceData functions available on the Trace Source, which you will not find on the basic Trace object. Hence the basic wrapper to give things feel closer to normal. The second parameter for the TraceEvent and TraceData functions is the event id, which maps to the Event ID column in the windows event log. You could of course have a whole bunch of standard ids for your app/domain and pass those along as required.

The calling code now looks like this:

[sourcecode language=”csharp”]

UltimaTrace.TraceInformation("Awaiting Orders");

UltimaTrace.TraceError("Could not persist order [{0}]: {1}", orderCommand, e);

[/sourcecode]