|
| |
Before web development was
popular and most applications were stand alone programs debugging strategies
depended a lot on how and where these were being developed. When developing in
C/C++ printf(...) was a favorite debugging tool on both UNIX and DOS
environments for programmers. That changed when Integrated Development
Environments - IDEs - like Borland's (now Inprise) Turbo C++ and Microsoft's
Visual Studio came along. These allowed stepping through code and watching local
and global variables in a GUI as each line of code was executed. Suddenly,
debugging was much more efficient. All that changed once again with the
web-programming model. Debugging was once again much harder and reduced to print
statements. Server side debugging before Visual Studio .NET was never up to the
mark. Even with Visual Studio .NET server side debugging still does not have all
the bells and whistles that the older IDEs had while developing stand-alone
applications. On the brighter side the .NET framework comes with an enhanced
library to aid in debugging and performance monitoring that makes debugging web
applications easier than before.In this
article we will review these classes and see how to use them. Although, we
specifically target web applications in this article most of all that we will
discuss applies to other programming models (like standalone programs) too. For
now, we will focus on debugging and tracing and discuss event logging and
performance monitoring in a later article. The code examples in this article
will use C# but the ideas are language independent and can be used in any
language that can be used to program in .NET
framework.The System.Diagnostics
namespace has almost all the classes we need for debugging and tracing. All of
these classes can be subdivided into five logical groups. The first group of
classes is the debugging helper classes that provide useful
methods to write code that is easy to debug and maintain. The second group of
classes provides tracing ability. Using these classes we are able
to write trace data into many different tracing systems simultaneously. The
third group of classes enables interaction with the Windows event
log. Using these classes it is possible to read and write to the
standard event logs (Application, Security, System) defined in Windows. It is
also possible to create new logs. The fourth group of classes provides the
ability to do performance monitoring. The fifth and last group
provide a set of classes to obtain process information.
Debugging Helper
ClassesThese classes provide the basic debugging
features. The following sections describe each of these classes in detail.
System.Diagonostics.ConditionalAttributeAttaching
metadata to code using attributes is a great feature in .NET. We can use this
feature to write code that is conditionally compiled and invoked during program
execution. Earlier, this was done in C/C++ using #ifdef statements. While this
method can still be used in C# it leads to unclean code since the code gets
sprinkled with preprocessor directives. The Java way of doing things is slightly
better than C/C++. In Java, debugging code is put within an if-else block as
shown
below.publicstaticboolean
debug =
true;if(debug) { MyDebugMethod();
// debugging action
here... }The
Java compiler is usually smart enough not to compile the call to the debugging
method and not include it in the byte code when the debug variable is set to
false. However, all that has been accomplished is the replacement of a
preprocessor directive of C/C++ with an if statement. Not a big
change.The
System.Diagonostics.ConditionalAttribute attribute provides a much cleaner way
of writing debugging code. Once methods that are exclusively used for debugging
have been defined they are marked with the conditional attribute as shown
below. [Conditional("DEBUG")]publicvoid
MyDebugMethod
() {//
debugging code
here... } When
the debug method needs to be called it can be called just like any other method.
The call is compiled by the C# compiler only if the compiler directive DEBUG has
been defined. This entirely localizes the debugging nature of the method instead
of the code globally being aware it. Thus, the code that uses this debugging
method needs to do no special handling (like preprocessor directives or if
statement). Usually, in Visual Studio .NET DEBUG and TRACE are both defined when
the debug build configuration is chosen. If it is necessary to define these
explicitly including the following statement at the top of any file can always
do it.
#define
DEBUGSystem.Diagonostics.BooleanSwitchThe
use of conditional attributes is fine when we need to do debugging during
application development. However, it does not allow us to turn debugging on or
off during execution time. To have such dynamic control we need to read in some
boolean value at run time and have our debugging methods called depending on the
variable. While this can be custom coded very simply, .NET provides a class,
System.Diagonostics.BooleanSwitch, specifically tailored for reading boolean
variables from configuration files. In the case of ASP.NET the web.config file
of the application should contain the definition and the value of the switch. An
example where a switch named “debugSwitch” is defined is shown
below. {In the case of stand-alone applications this switch should be defined in
the applications configuration
file. <configuration> ...
<system.diagnostics>
<switches>
<add name="DebugSwitch" value="1"
/>
</switches>
</system.diagnostics>
</configuration>Once the
switch has been defined in the configuration it can be declared in the
application as
follows.publicstatic
BooleanSwitch debug =
new
BooleanSwitch("DebugSwitch", "Debug
Switch");It is a good idea to make
these switches static so that they are read in only once. After they are
declared debugging code can be written as shown
belowif(debug.Enabled){//
debugging action
here...}In
this way debugging can be turned on and off without any recompilation. The only
slight penalty is that a boolean check is always performed irrespective of
whether debugging is on or
off.System.Diagostics.DebugThis
class provides methods for writing debugging output and validating assertions.
It has an Assert and Fail method with multiple signatures to suit different
situations. The methods of this class are defined with the Conditional attribute
discussed earlier. So the compiler variable DEBUG must be defined for these
methods to be called. In Visual Studio .NET DEBUG is defined by default during
debug builds. Thus, the use of the methods of this class does not affect
performance or size of release builds.When debugging output is written using
the four different Write methods in this class they go to all the listeners that
are registered with this class. All listeners that can trace debug output
inherit from TraceListener, which is an abstract class. There are three concrete
listeners that inherit TraceListener. The first listener, which is always
registered by default, is the DefaultTraceListener. This prints the output to
the debugger log during debugging. The second concrete listener is
EventLogTraceListener. This directs its output to an event log. The third
concrete listener is the TextWriterTraceListener. This prints its output to a
text file. When controlling the debugging trace with a
BooleanSwitch we have two options. One is to use a if-else statement as shown
below. if(traceflag.Enabled)
{ Debug.Write("Some
debugging
ouput"); }
The
other is to use the provided WriteIf methods of the Debug class.
Debug.WriteIf(debug.Enabled,
"Some debugging ouput");
Given these
two options it is always better to use the if-else structure for performance
reasons. When the traceflag is disabled the call to Write is never performed.
However, in the second construct the boolean check and the call is always
performed. Thus, for all practical purposes the Write methods should be used
exclusively.
Setting the appropriate
properties of the Debug class can control the format of the debugging output.
While all properties and listeners can be added in code it is best that they are
done in the config file. This allows for changes without recompilation. The
example config file shown above can be modified to add a text file log and fix
some formatting options as shown
below.
<configuration>
...
<system.diagnostics>
<switches>
<add name="debugSwitch" value="1"
/>
</switches> <debug
autoflush="true"
indentsize="4">
<listeners>
<add name="textListener"
type="System.Diagnostics.TextWriterTraceListener,System"
initializeData="c:\Debug.log"
/>
</listeners>
</debug>
</system.diagnostics>
</configuration>
System.Diagnostics.StackTrace
Many
times it is helpful to print out the call stack. This class provides the
relevant functionality. Unlike Java where a stack trace can only be obtained for
an exception by calling the printStackTrace method, the .NET framework provides
the ability to construct a stack trace anywhere in the code by constructing a
new instance of the StackTrace object. The call stack pertaining to an exception
can also be generated using the appropriate constructor. Once the StackTrace
object has been created the content can be printed out for
observation.
A StackTrace object is
essentially a collection of System.Diagnostics.StackFrame objects. Each
StackFrame object contains information about one call. The StackTrace object
exposes this collection using the FrameCount property and the GetFrame
method.
The following code prints out the
call stack using the StackTrace and StackFrame
class.
StackTrace st =
new
System.Diagnostics.StackTrace(true);
for(int
i = 0; i < st.FrameCount; i
++) { StackFrame
sf =
st.GetFrame(i); Debug.WriteLine("
File: " + sf.GetFileName()
+ " Line: " +
sf.GetFileLineNumber() +
" Method: " +
sf.GetMethod()); }
Tracing
The
most important class for tracing is System.Diagostics.Trace. The Trace class has
the same methods and properties as the Debug class except that its methods have
the Conditional attribute of TRACE. In Visual Studio .NET TRACE is defined in
both debug and release builds by default. So the methods of the Trace class are
always executed by default. So this class should be used only when we always
want the output.
As with Debug trace
output goes to all listeners that are registered with the Trace class. The same
listeners that are used for Debug can be used for
Trace.
The only other difference is that
while Debug has a binary nature of either being on or off Trace should be used
in a multilevel hierarchy. To do this a switch called TraceSwitch is defined.
This is similar to the BooleanSwitch class except that it has a level property
that can have five values defined in the TraceLevel enumeration. Combined with
the TraceSwitch class Trace can be used to provide flexible tracing of
applications as shown
below.
//
Create a
TraceSwitch. static
TraceSwitch traceSwitch =
new
TraceSwitch("trace",
"Application");
staticpublicvoid
TraceOutput()
{ //
Verbose
Tracing. if(traceSwitch.TraceVerbose) { Trace.Write("Trace
Output -
Verbose"); }
//
Error
Tracing if(traceSwitch.TraceError) { Trace.WriteLine("Trace
Output -
Error"); } }
As
always, though the switches, levels and listeners can be defined in code, they
should be defined in the applications config file so that they can be changed
whenever necessary without recompilation.
ConclusionThe System.Diagnostics namespace provides useful
classes for debugging applications. These become important specifically while
writing server side applications where IDE debuggers are difficult to use. These
classes provide extensive debugging, tracing, event logging and performance
monitoring features. In this article we
reviewed the debugging and tracing abilities. Event logging and performance
monitoring will be described in the next
article.Utpal
Chakraborty is Manager, Software Engineering at Organic (http://www.organic.com).
He has extensive experience in developing enterprise applications using
Microsoft and non-Microsoft technologies. He can be reached at uchakraborty@organic.com |
.NET Force is optimised for
Microsoft Internet Explorer 5 browsers.
Copyright © 2004 .NET Force.
Terms and Condition. All rights reserved.
|
 |
|