(Print this page)

Code diagnostic (an article on tracing and debugging)
Published date: Saturday, November 1, 2003
On: Moer and Éric Moreau's web site

When your application is crashing (unless yours never crash!), the more information you have, the easiest it is to debug and fix problems. You probably have already built a special version of your application to write some tracing/debugging info to a log file and removed that code short after you fix the error. What will you do if an error comes back in this area? Compile a tracing/debugging version again? I have something cool for you this month that won't need to recompile a special version. You will only need to change a character into the application configuration file and different level of tracing/debugging will be effective!

The Debug and the Trace classes belong to the System.Diagnostics namespace. These are the two classes you need to know to be able to either Debug or Trace your apps.

Debug or Trace?

If you take a look at the Debug Members from the help file and compare them to those of the Trace Members, you will surprisingly discover that both classes are identical.

The Debug class is normally dedicated to debug mode while the Trace is normally available to the debug and release modes. You can modify these behaviours by going to your project properties and check/uncheck appropriate checkboxes (Define DEBUG constant / Define TRACE constant) of the build panel from the Configuration Properties section. Calls to these classes are only compiled when the appropriate constant is defined.

Figure 1: Project Properties

All the members of these classes are shared (meaning that you don't need to declare an instance to use them).

Many ways to write

Four methods are available for you to write message. These are Write, WriteIf, WriteLine, WriteLineIf. These methods write to the listeners' collection that will be explained later. The Output Window is the default listener.

The two methods containing the Line keyword simply add a linefeed to your string. The two methods with the If keyword allows you to give a condition and the message only when the condition is true.

All these four methods have each four overloads:

  • WriteLine(Object)
  • WriteLine(String)
  • WriteLine(Object, Category As String)
  • WriteLine(String, Category As String) 

Note that the two methods with the If keyword have another parameter (as their first parameter) that is a Boolean expression.

The Category parameter is let you write something before the actual message. The category and the message will be separated with a colon (:).

These are various examples of some of the overloads of Write method:

Dim x As Integer = 5
'The following code outputs: The value of x is = 5
Trace.Write("The value of x is = ")
Trace.WriteLine(x)
'The following code outputs: This is the category: This is the message
Trace.WriteLine("This is the message", "This is the category")
'The following code outputs: This message only writes when x=5.
Trace.WriteLineIf(x = 5, "This message only writes when x=5.")

You can even indent

You can indent your messages easily by using the Indent method. Use the UnIndent method to decrease indent. Here is a sample of that:

Trace.WriteLine("This message will be written at level 0.")
Trace.Indent()
Trace.WriteLine("This message will be written at level 1.")
Trace.Indent()
Trace.WriteLine("This message will be written at level 2.")
Trace.Unindent()
Trace.Unindent()
Trace.WriteLine("Back to level 0.")

The output is:

This message will be written at level 0.
    This message will be written at level 1.
        This message will be written at level 2.
Back to level 0.

You can modify the number of spaces of an indent by setting the IndentSize property that default to 4.

The listeners' collection

Your messages are sent to the listeners' collection. The default listener display messages to the Output window. You can redirect the messages just about anywhere you want. The more common places are text files and the Windows event log. This is an example of setting the listener to send output to a text file:

'Create a new listener.
Dim objTL As New TextWriterTraceListener("c:\DemoTrace.log")
'Remove all existing listeners.
Trace.Listeners.Clear()
'Add the newly created listener to the collection.
Trace.Listeners.Add(objTL)
Trace.AutoFlush = True

There are several overloads of the constructor. The previous example use the constructor that gives a filename to which messages are to be written. If the file does not exist, it is automatically created for you. If the file exists, messages will be appended to the current content.

Notice that you can have any number of listeners running at the same time. If you don't clear the listeners' collection before adding your new one, the output will be sent to all the listeners. For example, if you remove the call to the Clear method from the previous example, messages will be sent to both the Output window and to the DemoTrace file.

You could also create your own listener by inheriting from the DefaultTraceListener class. Special formatting of the messages may be an example of a situation where you need to create your own.

I suggest setting the AutoFlush property to true, or else you will need to declare your TextWriter at a higher level and flush it yourself. Trace data are not written to the TextWriter until the listener is flushed or closed when the AutoFlush is set to False. Be aware that auto-flushing can degrade performance. In addition to the TextWriterTraceListener, EventLogTraceListener is also provided to you. For example, you add this line to the previous example to send the output to the event log:

Trace.Listeners.Add(New EventLogTraceListener("UTMag - Trace Test"))

The output can be found into the application section like the figure 2 is showing to you.

Figure 2: Output from the Event log

Personally, I prefer to use a text file because I find it easier to collect from remote PCs and easier to work with.

The configuration file

In the introduction, I told you that you could switch tracing on by simply changing a character from the configuration file. There are two switches you can use: the BooleanSwitch and the TraceSwitch.

The BooleanSwitch has two possible values: 

  • 0 = Disabled 
  • 1 = Enabled

The TraceSwitch gives you five values: 

  • 0 = Disabled 
  • 1 = Error 
  • 2 = Warning 
  • 3 = Info 
  • 4 = Verbose

The first thing to do is to create your configuration file (you know the App.Config file?) to your project. This file must contains something like this:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
	<system.diagnostics>
		<switches>
			<!--
				Boolean Switch values
					0 : Disabled
					1 : Enabled
			-->
			<add name="EricBoolSwitch" value="1" />
			
			<!--
				Trace Switch values
					0 : Disabled
					1 : Error messages
					2 : Warnings messages
					3 : Information
					4 : Verbose
			-->
			<add name="EricTraceSwitch" value="1" />
		</switches>
	</system.diagnostics>
</configuration>
You have to use all the tags given in this example or you test won't work. The things you may be changed are the name of the switches (EricBoolSwitch and EricTraceSwitch in my example) and their values (both set to 1 in my example).

Once your configuration file is created, you need to add code near the beginning of your project to read these values (notice that the first parameter of must match the name of the switch you created into the configuration file):

Private mBS As New BooleanSwitch( _
        "EricBoolSwitch", "This the Boolean switch.")
Private mTS As New TraceSwitch( _
        "EricTraceSwitch", "This is the Trace switch.")

Once your switches are declared, you can easily use them with the If versions of the Write statement.

The BooleanSwitch version must use the Enabled property like this:

Trace.WriteLine("The next line will test the Boolean switch.")
Trace.Indent()
Trace.WriteLineIf(mBS.Enabled, "The Boolean switch is ON.")
Trace.Unindent()

The TraceSwitch version must use one of the values of the TraceSwitch.Level enumeration. The messages will be sent to the listeners' collection if the level you specified is greater or equal to the level you wrote in the condition. For example, if you set the value to 2 into the configuration files, conditions testing for TraceError and

TraceWarning will return true. Here is an example:
Trace.WriteLine("The 4 following lines will test the Trace switch.")
Trace.Indent()
Trace.WriteLineIf(mTS.TraceError, _
      "Written when the switch is set to Error (or greater).")
Trace.WriteLineIf(mTS.TraceWarning, _
      "Written when the switch is set to Warning (or greater).")
Trace.WriteLineIf(mTS.TraceInfo, _
      "Written when the switch is set to Info (or greater).")
Trace.WriteLineIf(mTS.TraceVerbose, _
      "Written when the switch is set to Verbose.")
Trace.Unindent()

Conclusion

Debugging a multithreaded application is not easy. Using traces like I just demonstrated can greatly help you. There is nothing that will stop you tracing your application now without having to recompile them.

I hope you appreciated the topic and see you next month.


(Print this page)