Writing Debug and Trace Messages
Complex software can be made easier to debug and monitor with the use of logging code. Such code outputs information about an application to the screen or to a location that can be examined during debugging or when the software is in a live environment.
What are Debug and Trace Messages?
If you are developing complex software, it is useful to add logging capabilities. The logs generated can be viewed to determine the operation of the software that is difficult to understand through the user interface alone. When debugging, these logged messages can be very useful to help in identifying problems. To assist you in creating logs and monitoring the operation and performance of your software, the .NET framework includes facilities for outputting debug and trace messages.
Debug messages are created using the Debug class in the System.Diagnostics namespace. By default, when using the debugger within Visual Studio, these messages are displayed in the Output window if running software in debug mode. However, listeners can be added that write messages to other locations, such as text files. The methods used to generate the messages are decorated with the Condition attribute and the DEBUG symbol, so are not called when the program is compiled in release mode. This ensures that debug messages are not seen by the end-user and do not cause any performance impact.
Trace messages are written using the Trace class in the same namespace. They are different to Debug messages because they are included in the code when the TRACE symbol is defined, as it is by default for Visual Studio users. Trace messages can exist in code that has been compiled in either debug or release mode. They are useful when you do wish to log information from the software that you distribute to end-users.
The simplest use of debug messages is to display information in the Output window when running a program in the debugger. As the classes used to do this are found in the System.Diagnostics namespace, you should add the following using directive to the code:
You can now use the static Write and WriteLine methods of the Debug or Trace class to output information. The Write method can be used with a single parameter of any type. When a string is used, its contents are written to the Output window. With other types, the result of calling the object's ToString method is displayed. The following commands demonstrate the method's use for both the Debug and Trace classes.
Debug.Write("Application started (Debug)");
Trace.Write("Application stopped (Trace)");
The Write method outputs the string with no trailing carriage return or line feed. This allows several writes to be used to generate a single line of text. If you prefer to start a new line after writing a message, use the WriteLine method instead. As with Write, this method is available in both the Debug and Trace classes. For clarity, the remaining examples will show only the Debug variety.
You can categorise your messages by adding a second parameter to either the Write or WriteLine methods. This parameter should be a string containing any category name. When using the default listener, the category name is shown in the Output window before the message. For other listeners, the output may vary.
Debug.Write("Application started", "Debug");
Debug.WriteLine("Application stopped", "Debug");
To improve the readability of log files you can add indentation. It is often useful to match the indentation of the debug or trace messages to the indentation of your code, or to indent at the start of a method and remove the indentation before returning. Indentation can easily be included with the Indent and Unindent methods.
Debug.WriteLine("Application started", "Application");
Debug.WriteLine("Processing input XML", "File Import");
Debug.WriteLine("Imported 300 row(s)", "File Import");
Debug.WriteLine("Application stopped", "Application");
Writing Messages Conditionally
When outputting warning or error messages you will want to only log the message when certain conditions are met. Although it is possible to surround the Debug methods with if statements or switch commands, this can reduce the readability of the code and draw attention to the logging rather than the program logic. In some cases the additional checks will also remain in the code when compiled without the DEBUG or TRACE symbols.
To remove these problems, the Debug and Trace classes provide versions of the Write and WriteLine methods that only output a message when an initial parameter is true. These are named WriteIf and WriteLineIf. For example, the following only logs a message if the variable named "loggingEnabled" is set to true:
Debug.WriteLineIf(loggingEnabled, "Application started", "Application");
Using Trace Listeners
The Debug and Trace classes both include a property named Listeners. This property holds a collection of trace listeners that receive the messages from the methods already described. The two classes share the collection so adding a listener to the Debug class's collection adds it to the Trace class too.
In the previous examples the collection has contained a single listener that showed the messages using the Output window. This is useful but in many situations a different type of listener is required. In the final section of this article we will set up a TextWriterTraceListener. This type of listener appends the messages to a text file. There are many other types of listener available and you can also implement your own custom types. However, these topics are beyond the scope of this article.
A TextWriterTraceListener can be created with a single line of code, passing the name of the file to use as the only parameter of its constructor. If the file does not exist, it will be created. Once the listener is instantiated, you may add it to the Listeners collection to enable it. As you are now working with files that should be flushed and closed correctly, you should call the Close method before exiting from your application.
The following code sample shows an example use of a listener. This code is in the Main method of a Windows Forms application. Before the main form is opened, a TextWriterTraceListener is initialised and added to the collection of listeners. When the application is about to exit, the Close method is called to ensure that all messages are written to the file correctly. Any messages logged throughout the operation of the software will be appended to the named file.
NB: Messages are passed to all of the listeners in the collection. It is therefore possible to view the messages in the Visual Studio debugger as well as writing to a log file.
30 December 2009