Tracing


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 specific conditions. The problem with debugging is that a released program can behave differently. For 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’s 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, as 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 17-1 shows 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 TraceListener objects, and every listener has a TraceFilter connected.

image from book
Figure 17-1

Trace Sources

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.

Tip 

The TraceSource class is new since .NET 2.0. With .NET 1.0 you used the Trace and Debug classes to write trace information.

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 TraceInformation 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 only see 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() also supports passing a format string with any number of parameters in the same way as Console.WriteLine(). TraceInformation() does nothing more than invoke TraceEvent() with an identifier of 0. 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 not needed anymore, you can invoke the Close() method that closes all listeners associated with the trace source. Close() does a Flush() as well.

  TraceSource source1 = new TraceSource("Wrox.ProCSharp.Tracing"); source1.TraceInformation("Info message"); source1.TraceEvent(TraceEventType.Error, 3, "Error message"); source1.TraceData(TraceEventType.Information, 2, new int[] { 1, 2, 3 }); source1.Flush(); source1.Close(); 

Important 

You can use different trace sources within your application. It makes sense to define different sources for different libraries, so that you can turn on different trace levels for different parts of your application. For using a trace source you have to know its name. A commonly used name for the trace source is the same as the name of the namespace.

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, Critical. Critical defines a fatal error or application crash; Error, 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.

Trace Switches

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 BooleanSwitch, TraceSwitch, and SourceSwitch. The class BooleanSwitch can be turned on and off, while 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, Warning, Info and Verbose.

You can associate a trace switch programmatically by setting the Switch property of the TraceSource. Here the switch associated is of type SourceSwitch, has the name MySwitch, and the level Verbose.

 TraceSource source1 = new TraceSource("Wrox.ProCSharp.Tracing "); source1.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 System.Diagnostics.SourceSwitch associated with the name MySourceSwitch. The switch itself is defined within the <switches> section, and the level of the switch is set to verbose.

  <?xml version="1.0" encoding="utf-8" ?> <configuration>   <system.diagnostics>     <sources>       <source name="Wrox.ProCSharp.Tracing" switchName="MySourceSwitch"           switchType="System.Diagnostics.SourceSwitch" />     </sources>     <switches>       <add name="MySourceSwitch" value="Verbose"/>     </switches>   </system.diagnostics> </configuration> 

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 just written to the Output window of Visual Studio while you are running it in a debug session. Adding trace listeners changes this.

Trace Listeners

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 tracing should be written to is defined by trace listeners. A trace listener is derived from the abstract base class TraceListener.

Trace listeners defined by the .NET Framework are described in the following table.

Open table as spreadsheet

Trace Listener

Description

DefaultTraceListener

A default trace listener is automatically added to the listeners collection of the Trace class. Default output goes to the attached debugger. In Visual Studio, this is shown in the Output window during a debugging session.

EventLogTraceListener

The EventLogTraceListener writes trace information to the event log. With the constructor of the EventLogTraceListener, you can specify an event log source or an object of type EventLog. Event logging is described later in this chapter.

TextWriterTraceListener

With the TextWriterTraceListener trace, output can be written to a file, a TextWriter or a Stream. See Chapter 24, “Manipulating Files and the Registry,” for file manipulation information.

TextWriterTraceListener is the base class of ConsoleTraceListener, DelimitedListTraceListener, and XmlWriterTraceListener.

ConsoleTraceListener

ConsoleTraceListener writes trace messages to the console.

DelimitedListTraceListener

DelimitedListTraceListener writes trace messages to a delimited file. With trace output options, you can define a lot of separate tracing information such as process ID, time, and the like, which can easier be read with a delimited file.

XmlWriterTraceListener

Instead of using a delimited file, you can redirect the trace information to an XML file with the XmlWriterTraceListener.

IisTraceListener

The IisTraceListener is new in .NET 3.0

WebPageTraceListener

ASP.NET has another tracing option to get ASP.NET trace information about Web pages in a dynamically created output file trace.axd. If you configure the WebPageTraceListener, System.Diagnostics trace information goes into trace.axd as well.

.NET Framework delivers a lot of 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 messages to your mobile phone I guess that’s not that interesting. 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 to. The configuration here defines the XmlWriterTraceListener to write to the file demotrace.xml and the DelimitedListTraceListener to write to the file demotrace.txt.

 <?xml version="1.0" encoding="utf-8" ?> <configuration>   <system.diagnostics>     <sources>       <source name="Wrox.ProCSharp.Tracing" switchName="MySourceSwitch"           switchType="System.Diagnostics.SourceSwitch">         <listeners>           <add name="xmlListener"               type="System.Diagnostics.XmlWriterTraceListener"               traceOutputOptions="None" initializeData="c:/logs/demotrace.xml" />           <add name="delimitedListener" delimiter=":"               type="System.Diagnostics.DelimitedListTraceListener"               traceOutputOptions="DateTime, ProcessId"               initializeData="c:/logs/demotrace.txt" />         </listeners>       </source>     </sources>     <switches>       <add name="MySourceSwitch" value="Verbose"/>     </switches>   </system.diagnostics> </configuration>

With the listener you can also specify what additional information should be written to the trace log. This information is defined with the traceOutputOptions XML attribute and is defined by the TraceOptions enumeration. The enumeration defines Callstack, DateTime, LogicalOperationStack, ProcessId, ThreadId, and None. The information needed can be added with comma separation to the traceOutputOptions XML attribute, as shown with the delimited trace listener.

The delimited file output from the DelimitedListTraceListener, including the process ID and date/time is shown here:

 "Wrox.ProCSharp.Tracing":Information:0:"Info message"::4188:"":: "2007-01-23T12:38:31.3750000Z":: "Wrox.ProCSharp.Tracing":Error:3:"Error message"::4188:"":: "2007-01-23T12:38:31.3810000Z"::

The XML output from the XmlWriterTraceListener always contains the name of the computer, the process ID, the thread ID, 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.

Tip 

You can use the XmlDocument and XPathNavigator classes to analyze the content from the XML file. These classes are covered in Chapter 26, “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 the trace source. The name of the listener that is configured with as shared listener must be referenced from the listeners of the trace source:

 <?xml version="1.0" encoding="utf-8" ?> <configuration>   <system.diagnostics>     <sources>       <source name="Wrox.ProCSharp.Tracing" switchName="MySourceSwitch"           switchType="System.Diagnostics.SourceSwitch">         <listeners>           <add name="xmlListener"               type="System.Diagnostics.XmlWriterTraceListener"               traceOutputOptions="None" initializeData="c:/logs/demotrace.xml" />           <add name="delimitedListener" />         </listeners>       </source>     </sources>     <sharedListeners>       <add name="delimitedListener" delimiter=":"            type="System.Diagnostics.DelimitedListTraceListener"            traceOutputOptions="DateTime, ProcessId"            initializeData="c:/logs/demotrace.txt" />     </sharedListeners>     <switches>       <add name="MySourceSwitch" value="Verbose"/>     </switches>   </system.diagnostics> </configuration>

Filters

Every listener has a Filter property that defines whether 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 file, 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 the trace message should be written.

A filter is a class that is derived from the abstract base class TraceFilter. .NET 3.0 offers two filter implementations: SourceFilter and EventTypeFilter. 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 possible 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 configuration 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 EventTypeFilter. The XML listener specifies a SourceFilter 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.

 <?xml version="1.0" encoding="utf-8" ?> <configuration>   <system.diagnostics>     <sources>       <source name="Wrox.ProCSharp.Tracing" switchName="MySourceSwitch"           switchType="System.Diagnostics.SourceSwitch">         <listeners>           <add name="xmlListener" />           <add name="delimitedListener" />         </listeners>       </source>     </sources>     <sharedListeners>           <add name="delimitedListener" delimiter=":"               type="System.Diagnostics.DelimitedListTraceListener"               traceOutputOptions="DateTime, ProcessId"               initializeData="c:/logs/demotrace.txt">             <filter type="System.Diagnostics.EventTypeFilter"                 initializeData="Warning" />           </add>           <add name="xmlListener"               type="System.Diagnostics.XmlWriterTraceListener"               traceOutputOptions="None" initializeData="c:/logs/demotrace.xml">             <filter type="System.Diagnostics.SourceFilter"                 initializeData="Wrox.ProCSharp.Tracing" />           </add>     </sharedListeners>     <switches>       <add name="MySourceSwitch" value="Verbose"/>     </switches>   </system.diagnostics> </configuration>

The tracing architecture can be extended. Just as you can write a custom listener derived from the base class TraceListener, you can also create a custom filter derived from TraceFilter. With that capability, you can create a filter that specifies to write trace messages, for example, depending on the time, depending on an exception that occurred lately, or depending on the weather.

Asserts

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 help a lot if you write a library that is used by another developer.

With the Foo() method Trace.Assert() examines parameter o to see if it is not null. If the condition is false, the error message as shown in Figure 17-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.

image from book
Figure 17-2

  static void Foo(object o) {    Trace.Assert(o != null, "Expecting an object");    Console.WriteLine(o); } static void Bar(int x) {    Trace.Assert(x > 10 && x < 20, "x should be between 10 and 20");    Console.WriteLine(x); } static void Main() {    Foo(null);    Bar(3); } 

You can create an application configuration file with the <assert element to disable assert messages:

 <?xml version="1.0" encoding="utf-8" ?> <configuration>   <system.diagnostics>     <assert assertuienabled="false"/>   </system.diagnostics> </configuration>




Professional C# 2005 with .NET 3.0
Professional C# 2005 with .NET 3.0
ISBN: 470124725
EAN: N/A
Year: 2007
Pages: 427

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net