Saturday, March 7, 2009

Logging information - Trace Class


What are Trace statements
Trace statements are essentially printf style debugging. It allows to debug without a debugger. This kind of debugging was used before interactive debuggers were used.

How to use Trace class
Trace objects are active only if TRACE is defined-by default TRACE is defined in debug and release build projects in Visual Studio.

Similarly as Debug object, the Trace object uses TraceListeners to handle output.
If log data need to be stored in some particular way a custom trace listener can be implemented e.g. if data should be stored in database.
By default all logging information goes to the Output window. Let's see how to use a non-default listener, in this example we will use TextWriterTraceListener class.

FileStream objStream = new FileStream("C:\\AppLog.txt", FileMode.OpenOrCreate);
TextWriterTraceListener objTraceListener = new TextWriterTraceListener(objStream);
Trace.Listeners.Add(objTraceListener);
Trace.WriteLine("This is first trace message");
Trace.WriteLine("This is second trace message");
Trace.Flush();
objStream.Close();

In first line a FileStream object is created with file mode set to open or create.
Next the TextWriterTraceListener object is initialized with FileStream object.
To make it work the listener has to be added to Listeners collection of Trace class. Next the WriteLine method is used to log two text messages and finally the buffer is flushed to the output and FileStream is closed.
Notice that the default trace listener was not removed from the collection so whatever will be written using Trace class methods will go to all listeners from the collection, in this case will go to the Output window and will be logged to the AppLog.txt file.

How to control Trace from outside of application
Trace class can be controlled from outside of an application. Furthermore the configuration can be modified without rebuilding the application.
In order to configure trace we have to add the App.config file to the project which is basically XML file. After building a project the App.config file will change name to ApplicationName.exe.config where ApplicationName is actual application's Assembly name.

Let's take a look at following example.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.diagnostics>
        <trace autoflush="true" indentsize="2">
            <listeners>
                <remove name="Default"/>
                <add name="EventLogListener" type="System.Diagnostics.EventLogTraceListener" initializeData="MyEventLog"/>
            </listeners>
        </trace>
    </system.diagnostics>
</configuration>
All trace configuration should be placed under node. In above example we set two trace properties, the flush and the indent size. Next the default listener is removed, so the Trace will not write anything to the Output window. Next line we add a new listener of the type of EventLogTraceListener with a name of EventLogListener and we set an event source name to MyEventLog. Without writing a single line of code we added a a non-default trace listener. Isn't that great?

If above configuration is used you can find trace logs in the Event Viewer under Windows Logs/Application (click picture to enlarge).


How to use switches
Switches allow enabling or disabling tracing. In general there are two classes of switches BooleanSwitch (as name suggests has two possible states) and more interesting TraceSwitch class, which is basically multilevel switch with following possible levels
  • Off
  • Error
  • Warnings
  • Info
  • Verbose

There are two things which need to be done in order to use switches
  1. Define TraceSwitch object

    TraceSwitch theSwitch = new TraceSwitch("theSwitchName", "description");

    The first parameter is very important and enables to control the switch from outside of application. Second parameter is a description.
  2. Set the switch level.
    it can be done in the code e.g.

    theSwitch.Level = TraceLevel.Info;

    Or it can be configured from outside of application in already known config file, which is very useful since changes in configuration do not require rebuilding application!
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <system.diagnostics>
        <switches>
            <add name="theSwitchName" value="3" />
        </switches>
    </system.diagnostics>
</configuration>
Possible values of trace level
Trace Level Value
  • Off 0
  • Error 1
  • Warnings(& errors) 2
  • Info(warnings & errors) 3
  • Verbose (everything) 4
If the value of theSwitch is set to 2 only two first messages are logged ( for error and for warning ).
Trace.WriteLineIf(theSwitch.TraceError, "Error tracing is on!");
Trace.WriteLineIf(theSwitch.TraceWarning, "Warning tracing is on!");
Trace.WriteLineIf(theSwitch.TraceInfo, "Info tracing is on!");
Trace.WriteLineIf(theSwitch.TraceVerbose, "VerboseSwitching is on!");


To remove some some overhead we can use switch in a following way:
if ( theSwitch.TraceWarning )
{
    Trace.WriteLine( "Warning tracing is on!");
}

To use Trace statements in efficient manner we have to analyze how much information is needed to solve a problem on a machine without development environment. Too large log files will make process slow and on the other hand with too little information the problem cannot be solved.

No comments: