19.1 Logging and Assertion Facilities

The Debug and Trace classes provide error logging and assertion capabilities. These two classes are almost identical; the main differentiator is how they are used. The Debug class is used primarily in debug builds, while the Trace class is used in both debug and release builds.

For error logging and assertion, make use of the Debug and Trace classes where appropriate, then compile with the DEBUG symbol to enable the Debug class and the TRACE symbol to enable the Trace class. This is done using the /define:symbol compiler switch, or by using the #define preprocessor directive:

#define DEBUG // Equivalent to /d:DEBUG, activates Debug class
#define TRACE // Equivalent to /d:TRACE, activates Trace class

Note that using the compiler switch defines the symbols for all the source files in the compilation, while using the preprocessor directive defines only the symbol for the source file the directive appears in.

If these symbols are defined, the Debug and Trace methods function normally. If these symbols are not defined, many of the calls into the Debug and Trace classes are optimized away by the compiler.

Since the Debug and Trace classes are almost identical, we will concentrate on the Debug class in the rest of this section, identifying anything that is specific to either the Debug or Trace classes separately.

The Debug class maintains a collection of TraceListener instances that are responsible for handling (storing, reporting, etc.) application messages. Applications log status messages using the static Debug.WriteXXX methods, which forward the messages to each of the listeners in the Debug.Listeners collection. By default the Listeners collection includes a single listener (DefaultTraceListener), which outputs the message to the system debug console. However, the application is free to add and remove listeners to direct the messages elsewhere.

Adding and removing listeners can be done using a custom listener or one of the predefined listeners, such as the EventLogTraceListener, which logs messages to the Win32 event log, or the TextWriterTraceListener, which logs messages to a file or forwards them to another concrete type via an abstract TextWriter or Stream class, as the following code fragment demonstrates:

// Log messages to a file 
Debug.Listeners.Add(new TextWriteTraceListener("Debug.out")); 
TextWriter tw = Console.Out; // Get TextWriter for stdout
Debug.Listeners.Add(new TextWriterTraceListener(tw));
Stream stm = GetNetworkStream( ); // Connects to remote listener
Debug.Listeners.Add(new TextWriterTraceListener(stm));

Messages are written using the Write, WriteLine, WriteIf, and WriteLineIf overloads. Messages may optionally be assigned to a category, allowing listeners to group related messages. Messages may also be indented. Examples of using these methods are as follows:

Debug.Write("Send this to all listeners, uncategorized");
Debug.WriteLine("Error", "Group this message with other errors");
Debug.Indent( );
Debug.WriteLineIf(bLogging, "Hug a tree (indented)"); // conditional
Debug.Unindent( );

When using listeners that may require cleanup, such as the TextWriterTraceListener, remember to call the Flush and Close methods on application shutdown, as follows:

Debug.Flush( ); // Flush underlying FileStream
Debug.Close( ); // Close Debug.out file

A useful technique for detecting error conditions early is using asserts to verify class or method invariants. The Debug and Trace classes both provide Assert and Fail methods that test expressions and can optionally invoke the debugger or log messages when an assertion fails, as follows:

Debug.Assert(expression); // Use to check class or method invariants
Debug.Assert(false, "Alert the listeners!"); // Log invalid assertions
Trace.Fail("Unknown error - about to die horribly");
Trace.Assert(veryImportantInvariant); // Asserts in release builds too...

    Part II: Programming with the .NET Framework
    Part IV: API Quick Reference