With tracing you can see messages from the running application. To get some information about a running application, you can start the application in the debugger. During debugging, you can . walk through the application step by step and set breakpoints at specific lines and when you reach
example, while the program is stopping at a breakpoint, other threads of the application are suspended as well. Also, with a release build, the compiler-generated output is optimized and thus different effects
can occur. There is a need to have information from a release build as well. Trace messages are written both with debug and release code.
A scenario showing how tracing helps is described here. After an application is deployed, it runs on one system without problems, while on another system intermediate problems occur
. Turning on verbose tracing on the system with the problems gives you detailed information about what’s happening inside the application. The system that is running without problems has tracing configured just for error messages redirected to the Windows event log system. Critical errors are seen by the system administrator. The overhead of tracing is very small, because you configure a trace level only when needed.
The tracing architecture has four major parts:
¤ The source is the originator of the trace information. You use the source to send trace messages
¤ The switch defines the level of information to log. For example, you can request just error information or detailed verbose information
¤ Trace listeners define where the trace messages should be written
¤ Listeners can have filters attached. The filter defines what trace messages should. be written by the listener. This way, you can have different listeners for the same source that write different levels of information.
Figure 18-1shows the major classes for tracing and how they are connected in a Visual Studio class diagram. The TraceSource uses a switch to define what information to log. The TraceSource has a TraceListenerCollection associated where trace messages are forwarded to. The collection consists ‘of TraceListeneJ:; objects, and every listener has a TraceFil ter connected .
You can write trace messages with the TraceSource class. Tracing requires the Trace flag of the compiler settings. With a Visual Studio project, the Trace flag is set by default with debug and release builds, but you can change it through the Build properties of the project.
The TraceSource class is more difficult to use compared to the Trace class writing trace messages, but it provides more options.
To write trace messages, you need to create a new TraceSource instance. In the constructor, the name of the trace source is defined. The method TraceInforrnation () writes an information message to the
trace output. Instead of just writing informational messages, the TraceEvent () method requires an enumeration value of type TraceEventType to define the type of the trace message. TraceEventType . Error specifies the message as an error message. You can define it with a trace switch to see only error messages. The second argument of the TraceEvent () method requires an identifier. The ID can be used within the application itself. For example, you can use id 1 for entering a method and id 2 for exiting a method. The method TraceEvent () is overloaded, so the TraceEventType and the ID are the only required parameters. Using the third parameter of an overloaded method, you can pass the message written to the trace. TraceEvent () alfo supports passing a format string with any number of
parameters in the same way as consoie. WriteLine (). TraceInformation () does nothing more than invoke TraceEvent () with an identifier of O.TraceInformation () is just a simplified version of
TraceEvent (). With the TraceData () method, you can pass any object, for example an exception instance, instead of a message. To make sure. that data is written by the listeners and does not stay in memory, you need to do a Flush ().If the source is no longer needed, you can invoke the Close () method that closes all listeners associated with the trace source. Close () does a Flush () as well.
The TraceEventType enumeration that is passed as an argument to the TraceEvent () method defines the following levels to specify the severity of the problem: Verbose, Information, Warning, Error, and Cri tical. Critical defines a fatal error or application crash; Error defines a recoverable error. Trace messages at the Verbose level give you detailed debugging information. TraceEventType also defines action levels Start, Stop, Suspend, and Resume. These levels define timely events inside a logical operation.
The code, as it is written now, does not display any trace message because the switch associated with the trace source is turned off
To enable or disable trace messages, you can configure a trace switch. Trace switches are classes that are derived from the abstract base class Switch. Derived classes are BooleanSwi tch, TraceSwi tch, and SourceSwi tch. The class BooleanSwi tch can be turned on and off, and the other two classes provide a range level that is defined by the TraceLevel enumeration. To configure trace switches, you must know the values associated with the TraceLevel enumeration. TraceLevel defines the values Off, Error, \’iarning, Info, and Verbose.
Youcan associate a trace switch programmatically by setting the Switch property of the TraceSource. Here the switch associated is of type SourceSwi tch, has the name MySwit ch, and has the level Verbose:
TraceSource sourcel new TraceSource(“Wrox.ProCSharp.Tracing”);
sourcel.Switch = new SourceSwitch(“MySwitch”, “Verbose”);
Setting the level to Verbose means that all trace messages should be written. If you set the value to Error, only error messages should show up. Setting the value to Information means that error, warning, and info messages are shown. Writing the trace messages once more, you can see the messages while running the debugger in the Output window.
Usually, you would want to change the switch level not by recompiling the application, but instead by changing the configuration. The trace source can be configured in the application configuration file. Tracing is configured within the <system. diagnostics> element. The trace source is defined with the <source> element as a child element of <sources>. The name of the source in the configuration file must exactly match the name of the source in the program code. Here, the trace source has a switch of type Sys tern. Diagnos tic s . SourceSwi tch associated with the name MySourceSwi tch. The switch itself is defined within the <swi tches> section, and the level of the switch is set to verbose.
Now, you can change the trace level just by changing the configuration file without the need to recompile the code. After the configuration file is changed, you must restart the application. Currently, trace messages are written to just the Output window of Visual Studio while you are running it in a debug session. Adding trace listeners changes this.
By default, trace information is written to the Output window of the Visual Studio debugger. Just by changing the application configuration, you can redirect the trace output to different locations.
Where tracin~ should be written to is defined by trace listeners. A trace listener is derived from the abstract base class TraceListener. .
Trace listeners defined ~y the .NET Framework are described in the following table
.NET Framework delivers many listeners to which trace information can be written. In case the listeners don’t fulfill your requirements, you can create a custom listener by deriving a class from the base class
.TraceListener. With a custom listener, you can, for example, write trace information to a Web service, write message; to your mobile phone … I guess it’s not that interesting to receive hundreds of messages to your phone in your spare time. And with verbose tracing this can become really expensive. You can configure a trace listener programmatically by creating a listener object and assigning-it to the
Listeners property of the TraceSource class. However, usually it is more interesting to just change a configuration to define a different listener. You can configure listeners as child elements of the <source> element. With the listener, you define the type of the listener class and use initializeData to specify where the output of the listener should
go. The configuration here defines the XmlWriterTraceListener to write to the file demotrace. xml a.nd the Delimi tedListTraceListener to write to the file demotrace. txt:
You might get a wa’ning from the XML schema regardine the delimiter attribute declaration. You can ignore it.
With the listener, you can also specify what additional information should be written to the tr~ce log. This Information is defined with the traceOutputOptions XML attribute and is defined by the TraceOpt ions enumeration. The enumeration defines Callstack, DateTime, LogicalOperationStack, Processld, Threadld, and None. The information needed can be added with comma separation to the traceOutputOptions XML attribute, as shown with the delimited trace listener.
T1W delimited file output from the D~limi tedListTraceListener, including the process ID and datei’1 ‘5 shown here:
·Wrox.P.roCSharp. Tracing” :Information: 0: ‘Info message’: :4188: ” : :
The XML output from the Xm’lWriterTraceLis tener always contains the name of the computer, the process-lb. the thread !D, the message, the time created, the source, and the activity ID. Other fields, such as the call stack, logical operation stack, and timestamp, depend on the trace output options.
You ctm use the Xm,lDocumenta~d XPathNavigator classes to analyze the content from the XML file. These classes are covered in Chapter 28, “Manipulating XML.”
If a listener should be used by multiple trace Sources, you can add the listener configuration to the element <sharedListeners>, which is independent of tl)e trace source. The name of the listener that is configured with a shared listener must be referenced from the listeners of the trace source:
Every listener has a Fi 1ter property that defines whethet the listener should write the trace message. For example, multiple listeners can be used with the same trace source. One of the listeners writes verbose messages to a log me, and another listener writes error messages to the event log. Before a listener writes a trace message, it invokes the ShouldTrace () method of the associated filter object to
decide if tlle trace message should be written.
A filter is a class that is derived from the abstract base class TraceFil ter ..NET 3.0offers two filter implementations: SourceFil ter and EventTypeFil ter. With the source filter, you can specify that trace
messages are to be written only from specific sources. The event type filter is an extension to the switch functionality. With a switch, it is pessible to define, according to the trace severity level, if the event source should forward the trace.message to the listeners. If the trace message is forwarded, the listener now can use the filter to-decide if the message should be written. The changed co¢iguration now defines that the delimited listener should write trace messages only if the severity level is of type warning or higher, because of the defined EventTypeFil ter. The XMLlistener specifies a SourceFil ter and accepts trace messages only from the source Wrox. ProCSharp. Tracing. In case you have a large number of sources defined to write trace messages to the same listener, you can change the configuration for the listener to concentrate on trace messages from a specific source.
The tracing architecture can be extended. Just as you can write a custom listener derived fmm the base class TraceListener, you can also create a custom filter derived from TraceFil ter. With that capability, you can create a filter that specifies to write trace messages, for example, depending on the time~epending on an exception that occurred lately, or depending on the weather.
Another feature that belongs to tracing are asserts. Asserts are critical problems within the program path. With asserts, a message is displayed with the error, and you can abort or continue the application.
Asserts are very helpful when you write a library that is used by another developer. With the Foo () method, Trace. Assert () examines parameter 0 to see if it is not null. If the condition is false, the error message as shown in Figure 18-2 is issued. If the condition is true, the program
continues. The Bar () method includes a Trace. Assert () example where it is verified that the parameter is larger than 10 and smaller than 20. If the condition is false, an error message is shown again.
You can create an application configuration file with the <assert> element to disable assert messages: