This month's .NET Nuts & Bolts covers the different .NET Framework options for diagnosing issues within your applications. It touches on a couple of the classes available in the System.Diagnostics namespace and the functionality they provide for debugging and tracking an application's execution path.
For those who have been programming long enough, you probably recall a time prior to advanced development tools such as Visual Studio when diagnosing application issues was more complicated. It was a time when you could not step through your code, nor determine exactly where the application was in its execution. One of the techniques often used then was embedding a series of print statements within the code to display the output at any given point in time and track the application logic flow. These statements had to be removed prior to application deployment, but one or two invariably would remain and cause embarrassment when displayed. Microsoft has included the Debug and Trace classes in the. NET Framework to help with this style of debugging and to avoid some of the pitfalls.
Modern Day Debugging
The System.Diagnostics.Debug class provides a set of methods and properties for debugging code. The methods in the Debug class offer a number of very useful options. One of them is that it displays the messages only when the code is compiled as a debug build. (Note: the debug build option is compiler specific. In C#, it is /d:DEBUG. In Visual Basic .NET, it is /d:DEBUG=True. I prefer just to let Visual Studio control it for me by changing the mode through Configuration Manager.) The result is that the debug statements can remain in the code at all times and can be easily excluded from production code simply by compiling without the debug option.
System.Diagnostics.Debug Sample Code
The following are some of the more common methods available in the Debug class:
- Assert—Displays a dialog message if the condition being checked is false
- Write/WriteLine—Writes information with or without a new line
- WriteIf/WriteLineIf—Conditionally writes information with or without a new line
- Indent/Unindent—Indents or unindents the debugging output
The following sample code demonstrates all the methods listed above. The Assert statement results in a dialog box (Figure 1), while the others result in output being displayed in the Debug section of the Output window (Figure 2):
System.Diagnostics.Debug.Assert(false, "Message", "Detail message to show in the debug window."); Random rand = new Random(100); System.Diagnostics.Debug.Indent(); System.Diagnostics.Debug.WriteLine("First random numbeCr: " + rand.Next().ToString()); System.Diagnostics.Debug.Unindent(); System.Diagnostics.Debug.WriteLineIf(rand.Next()%2 == 0, "Even random number generated.");
Figure 1: Assert Output
Figure 2: Debug Output
Using a Configuration File
Another handy System.Diagnostics.Debug option is the ability to control the Debug class through the application's configuration file (web.config or app.config). A <system.diagnostics> element allows control of the behavior. The following example sets the size of indentions when printing and controls whether or not the Assert method generates a dialog:
<system.diagnostics> <trace autoflush="true" indentsize="7" /> <assert assertuienabled="false" /> </system.diagnostics>
When you add the items above to your configuration file and execute it, you see slightly different output. Instead of the dialog from Figure 2, the output displays larger indention than the default (see Figure 3).
Figure 3: Control Debug Through Configuration
The System.Diagnostics.Trace class provides a set of methods and properties for tracing the execution of code. These statements are often handy when you deal with complex logic and aren't sure where the code is at in execution. Trace statements are very similar to Debug statements. Trace exposes the same methods and functionality as the Debug class offers. The difference is that Trace is enabled by default for both debug and release builds. Thus, Trace statements can be used to trace the execution of code when it has been released to production. This can be handy for finding additional information on how your application is behaving in production.
System.Diagnostics.Trace Sample Code
Because the Debug and Trace classes have the same methods, I just changed the sample code above to use Trace instead of debug. The output will be the same, so I won't bother to include the figures again:
System.Diagnostics.Trace.Assert(false, "Message", "Detail message to show in the debug window."); Random rand = new Random(100); System.Diagnostics.Trace.WriteLine("First random number: " + rand.Next().ToString()); System.Diagnostics.Trace.Indent(); System.Diagnostics.Trace.WriteLineIf(rand.Next()%2 == 0, "Even random number generated."); System.Diagnostics.Trace.Unindent();
The previous examples are all well and good as long as you're running the code in Visual Studio so that you can see the output window. This may work when debugging on the local machine, but it won't be an option when the application is in production. Thus, you need to be able to control where the output from Debug and Trace statements is written. This is done through what is known as a listener. A listener does just as its name implies: It listens for debug or trace output and then behaves accordingly. You can use a TextWriterTraceListener class to write Debug or Trace output to a TextWriter or a Stream, which can result in output being written to the console, a file, and so forth. You also can use an EventLogTraceListener to send messages to the Windows Event Log. Finally, you have an abstract TraceListener class that you can implement to create custom listeners for monitoring debug and trace output.
Listener Sample Code
The following sample code extends the previously given example code to have the output written to the console window rather than the Output window of the debugger (see Figure 4). It also demonstrates how to send the output just to the console:
// Add a listener to send output to the console System.Diagnostics.TextWriterTraceListener writer = new System.Diagnostics.TextWriterTraceListener(System.Console.Out); System.Diagnostics.Debug.Listeners.Add(writer); // Remove the default listener and only send to the console System.Diagnostics.Debug.Listeners.Remove( System.Diagnostics.Debug.Listeners); // Output debug messages System.Diagnostics.Debug.Assert(false, "Message", "Detail message to show in the debug window."); Random rand = new Random(100); System.Diagnostics.Debug.Indent(); System.Diagnostics.Debug.WriteLine("First random number: " + rand.Next().ToString()); System.Diagnostics.Debug.Unindent(); System.Diagnostics.Debug.WriteLineIf(rand.Next()%2 == 0, "Even random number generated.");
Figure 4: Console Output
Beyond the basics of using the Debug and Trace classes covered here, consider the following enhancements:
- Set up a listener through the configuration file—Rather than set up the listener via code, experiment with controlling it through the <listeners> element in the application configuration file.
- Set up the EventLogTraceListener—Configure the listener to output to the event log.
- Implement a custom Listener—Create additional listeners to log to alternative locations such as a database.
The topic of the next column is yet to be determined. If you have something in particular that you would like .NET Nuts & Bolts to explain, contact me at firstname.lastname@example.org.