The .NET
Framework Class Library (FCL) contains many classes to obtain
diagnostic information about your application, as well as the
environment it is running in. In fact, there are so many classes that
a namespace,
System.Diagnostics
,
was created to contain all of them. This chapter contains recipes for
instrumenting your application with debug/trace information,
obtaining process information, using the built-in Event Log, and
taking advantage of performance counters.
Debugging (using the
Debug
class) is turned on by default in the debug
build only, and
tracing (using
the Trace
class) is turned on by default in both
debug and release builds. These defaults allow you to ship your
application instrumented with tracing code using the
Trace
class. You ship your code with tracing
turned off so that the tracing code is not called (otherwise, the
tracing would slow your application). If a problem that you cannot
recreate on your development computer occurs on the production
machine, you can enable tracing and allow the tracing information to
be dumped to a file. This file can be inspected to help pinpoint the
real problem. This trick is discussed at length in Recipe 6.1 and Recipe 6.2.
Since both the Debug
and Trace
classes contain the same members with
the same names, they can be interchanged in your code by renaming
Debug
to Trace
and vice versa.
Most of the recipes in this chapter use the Trace
class; you can modify those recipes so that they use the
Debug
class by replacing each
Trace
with Debug
in the code.
Mysterious bugs often appear at the client’s site, even after the application is thoroughly tested. Most of the time these bugs are difficult, if not impossible, to reproduce on your development machine. Knowing this, you want an application with built-in instrumentation that’s off by default but can easily be turned on when you need it.
Use the Trace
class for any tracing code that you
might need to turn on after your application has been deployed. To
turn on tracing at a client’s site, provide the
client with an
application configuration file such as
this one:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.diagnostics> <switches> <add name="DatabaseSwitch" value="4"/> <!-- 4 == TraceLevel.Verbose --> </switches> <trace autoflush = "true" indentsize = "2"> <listeners> <add name = "MyListener" type = "System.Diagnostics.TextWriterTraceListener" initializeData = " MyFileName.log"/> </listeners> </trace> </system.diagnostics> </configuration>
Allowing tracing code to be enabled and used at a client site can be
incredibly useful when debugging problems in release code. This
technique is even more useful when the problem cannot easily be
reproduced in-house. For this reason, it is—in some
cases—a wise practice to use the Trace
class
instead of the Debug
class when adding tracing
code to your application.
To control the trace output at a client site, you can use an
XML
config file. This XML file must have the same base name as the
executable that is to use these switches, followed by an extension of
.exe.config
. For example, if the executable name
were Accounting.exe
, the configuration file
would be named Accounting.exe.config
. This file
should be placed in the same directory as the executable
Accounting.exe
.
The application configuration file always consists of the following two outer elements for diagnostic information:
<configuration> <system.diagnostics> ... </system.diagnostics> </configuration>
However, the configuration
element may contain
other child elements besides the
system.diagnostics
element.
Within these elements, the switches
and
trace
elements may be added. These two elements
contain information specific to switches and listeners. If your code
contains a TraceSwitch
(as shown in the next
example) or BooleanSwitch
object—or any
other type derived from the Switch
class—you
can control this object’s trace level setting
through the
<switches>
element in the configuration
file:
private static TraceSwitch ts = new TraceSwitch("DatabaseSwitch", "Only allow database transactions to be logged");
The <listeners>
element shown in the Solution adds a new
TraceListener
derived object to the listeners
collection. Any Trace
or Debug
statements will use this new listener.
The switches
element of the Solution can contain
the three elements defined here:
<clear/>
Clears any previously added switch.
<add name=
"Switch_Name
" value=
"Number
"/>
Adds new switch initialization information to be used at runtime. The
name
attribute defines the name of the switch that
is used in your code. The value
attribute is set
to a number that either turns the switch on or off, in the case of a
BooleanSwitch
class, or defines the switch level
(e.g., the amount of output you wish to receive), in the case of a
TraceSwitch
class. To turn on a
BooleanSwitch
, use a nonzero value (negative
numbers work here, too); to turn it off, use zero.
<remove name=
"Switch_Name
"/>
Removes switch initialization information at runtime. The
name
attribute defines the name of the switch that
is used in your code.
Immediately after the
switches
tags in the solution are the
trace
tags, although the ordering of these tags is
up to you. The trace
tags can contain the
following two optional attributes:
autoflush = true|false
Indicates whether the listener automatically flushes its buffer after
every write (true
) or not
(false
).
indentsize = "4
"Specifies the number of indent characters to use when indenting the output.
Within the trace
tags
are the listeners
tags, which, in turn, can
contain any of the following defined tags:
<clear/>
Clears any previously added listeners. This tag also removes the
DefaultTraceListener
from the listeners
collection.
<add name=
"MyListener
" type="System.Diagnostics.TextWriterTraceListener,System" initializeData=
"MyFileName.log
"/>
Adds a new listener to any Trace
and
Debug
classes used in your application. The
name
attribute defines the name of the listener
that is used in your code. The type
attribute is
set to the listener’s class name. The optional
initializeData
attribute allows a string to be
passed in to the constructor of this listener. If you are using a
custom listener, you will need to include a constructor that accepts
a string as the only argument to prevent an exception from being
thrown.
<remove name = "MyListener"/>
Removes a listener at runtime. The name
attribute
defines the name of the listener to be removed. This could be useful
if another configuration file, such as the
machine.config
file, has already added a
listener or if any listeners were created through your
application’s code. If more than one listener is
added, the output will be written out twice—once for each
listener.
Regardless of whether your code defines TRACE
and/or DEBUG
, the code will attempt to access this
file for switch initialization information if a class derived from
Switch
is instantiated. If you wish to prevent
this behavior, place any code that instantiates a switch class inside
of a method decorated with the
ConditionalAttribute
attribute:
public class Traceable { BooleanSwitch DBSwitch = null; BooleanSwitch UISwitch = null; BooleanSwitch exceptionSwitch = null; [System.Diagnostics.ConditionalAttribute("TRACE")] public void EnableTracing( ) { DBSwitch = new BooleanSwitch("DatabaseSwitch", "Switch for database tracing"); UISwitch = new BooleanSwitch("UISwitch", "Switch for user interface tracing"); exceptionSwitch = new BooleanSwitch("ExceptionSwitch", "Switch for tracing thrown exceptions"); } }
The ConditionalAttribute
attribute prevents the
switches from attempting to access the application configuration file
when TRACE
is undefined by preventing your
application from calling the EnableTracing
method.
In addition to the application
configuration file (MyApp.exe.config
), there is
also a machine.config
file located in the
directory %<runtime install
path>%CONFIG
. The configuration tags, and all of its
containing elements may be placed in this file as well. However,
doing so will enable these switches and listeners on a machine-wide
level. This can cause applications that define their own listeners to
behave strangely, especially if the listeners are duplicated.
Additionally, the application will look for configuration information
in the application configuration file first and the
machine.config
file second.
The application configuration file and the machine configuration file
are both case-sensitive. Be sure that your tag names and their
attributes are in the correct case. However, the string assigned to
the name
attribute does not seem to be
case-sensitive, while other strings assigned to attributes are
case-sensitive.