Easy TraceLogging In C#

The .NET Framework includes some convenient ways for tracing and debugging. In this post, I'll discuss how to easily add tracing functionality to your apps to output data to a text file.

First, define a trace switch in your App.config file. This should be placed in the system.diagnostics node, and the point of the switch is to specify the level at which to output data with the switch. Here's an example:

  0 = Off
  1 = Error
  2 = Warning
  3 = Info
  4 = Verbose
    <add name="MainTraceSwitch" value="4"/>

There are five possible values:

0 = Off
1 = Error
2 = Warning
3 = Info
4 = Verbose

So, if you set the value of your switch to 1, you're telling it that you only want to trace on errors. If you set it to 4, you're telling it to trace everything. Basically, you'll check this value in your code before you write any output to your trace file. The name attribute (which is set to "MainTraceSwitch" in our example above) is used to reference the switch in your code. This way, you can define multiple switches, each with their own value. If one part of your app is being problematic, you could set the switch you use there to Verbose, while leaving the other switches at lower levels.

Next, you'll want to define TraceSwitch and TextWriterTraceListener objects. Add these inside the class you'll be using them from, and outside of any of the class's methods so they'll be accessible anywhere in the class. These classes reside in the System.Diagnostics namespace, so adding a using reference to the top of your class will save you some typing.

TraceSwitch mMainTraceSwitch;
TextWriterTraceListener mMainTraceListener;

Now that we have the objects we'll be using for our trace logging, we need to set them up. I usually do this all in a seperate procedure which I call from within the class constructor.

Instantiate the TraceSwitch using the name you gave it in the App.config file, and a meaningful description, as shown below:

mMainTraceSwitch = new TraceSwitch("MainTraceSwitch", "The app's main TraceSwitch");

Next, instantiate the TextWriterTraceListener by giving it the name of a text file to write its output to. In the example below, sLogDir represents a string variable containing the directory to place the log file in, and I'm creating a log file which includes a timestamp as part of the filename.

mMainTraceListener = new TextWriterTraceListener(sLogDir + "LogFile" + DateTime.Now.ToString("yyyyMMddHHmmss") + ".txt");

I like to set the static Trace.AutoFlush parameter to true so I don't need to flush the output stream manually (in other words, I want the data written to the file as soon as I give it to the TraceSwitch). You don't need to do this, but I find it easier this way.

Trace.AutoFlush = true;

For the TextWriterTraceListener to be usable, you need to add it to the static Trace.Listeners collection. You can do this by passing the TextWriterTraceListener to the Trace.Listeners.Add() method, as shown below:


All that remains is to add code that actually makes use of everything we've set up. Generally, this is done by checking the value of the TraceSwitch's Level property. The value of this property is a member of the TraceLevel enumeration, which corresponds to the trace levels we discussed earlier in the App.config file. For example, you could have a line of code that does the following:

if (mMainTraceSwitch.Level == TraceLevel.Info) Trace.WriteLine(DateTime.Now.ToString() + " - Processing input file");

In the above example, if the TraceLevel is set to TraceLevel.Info, output is written to your trace file using the static Trace.WriteLine() method.

An even more convenient way to check the trace level is to use any of the following properties of the TraceSwitch: TraceError, TraceInfo, TraceVerbose, and TraceWarning. The benefit here is that the levels are aggregated. For example, if the trace level is set to TraceInfo (3), then the TraceInfo and TraceWarning properties will also be true, because the TraceInfo setting is higher up in the list. If, for example, the trace level was set to TraceWarning (2), the TraceError property would be true, but TraceInfo and TraceVerbose would both be false.

Here's an example of doing it with one of the level-specific properties:

if (mMainTraceSwitch.TraceError) mMainTraceListener.WriteLine(DateTime.Now.ToString() + " - ERROR in ProcessInput(): " + ex.ToString());

It's also worth noting that you can write the output either with the static Trace.WriteLine() method, or the WriteLine() method of the TextWriterTraceListener. Trace.WriteLine() will write to all of the listeners in the Trace.Listeners collection, whereas the WriteLine() method of the TextWriterTraceListener class writes only to that specific listener. Note however that if you use the WriteLine() method of the specific listener that you will need to manually flush the stream (via the Flush() method) before anything is written to the output file.


Popular Posts

How To Mock Out Child Components In Unit Tests of Angular 2 Code

A Generic Method Using HttpClient to Make a Synchronous Get Request

The Cause and Solution for the "System.Runtime.Serialization.InvalidDataContractException: Type 'System.Threading.Tasks.Task`1[YourTypeHere]' cannot be serialized." Exception

A Red Herring When Using Moq to Mock Methods With Optional Parameters

Unit Testing with a Mock Entity Framework DbContext and Fake DbSets