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...
|