Code Tracing

 < Day Day Up > 



The next code instrumentation technique that we will analyze is tracing. In a multi-threaded application, this technique is especially important. You can trace a thread's behavior and interaction when more than one task has been started. As we will see later, this is not possible using the debugger. The .NET Framework provides some useful classes that allow developers to implement tracing functionality simply. Let's examine the tracing classes from the System.Diagnostics namespace that the .NET Framework offers:

  • Trace: This class has many static methods that write messages to a listener. By default, the debug output windows in VS.NET will be used as the listener application, but thanks to the Listeners collection, you can add different listeners such as a text file listener, or the Windows event log listener.

  • Debug: This class has the same methods as the Trace class, writing information to a listener application. The largest difference between these two classes is in their usage; Trace is useful at run time, Debug is used at development time.

  • BooleanSwitch: This class allows us to define a switch that turns on or off the tracing messages.

  • TraceSwitch: This class provides four different tracing levels allowing developers to choose the severity of the messages to send to the listener.

The Trace Class

In this section, we will analyze the most frequently used methods of the Trace class. It is provided by the .NET Framework and encapsulates all the necessary methods to implement tracing functionality easily. The Trace class is contained in the System.Diagnostics namespace and provides many static methods for sending messages to the listener application. As you know, static methods mean that you do not have to instantiate a new object from the Trace class and can use the method directly. For example:

    static void Main()    {      Trace.WriteLine(t.ThreadState);    } 

The code snippet above uses the WriteLine () method to output the thread state, followed by a carriage return, to the listener application. The following table lists some of the other static methods provided by the Trace class:

Method

Description

Assert (condition, message)

Displays the specified string message when the condition provided to the method evaluates to false. When you do not specify the message text, the Call Stack is displayed instead.

Fail (message)

Similar to the Assert() method, this writes the specified text to the Call Stack when a failure occurs. The Assert() method differs because Fail() cannot specify a condition before displaying the error. In fact, the Fail() method is usually placed in the catch statement of a try-catch-finally instruction. You could also place it anywhere in your code that you are sure could never be reached - such as in the default case of a switch statement where you believe you've allowed for all possibilities.

Write (message | object)

Writes the specified text message, or object name, to the listener application.

WriteIf (condition, message)

Writes the specified message text into the listener application if the specified condition is true.

WriteLine (message | object)

Writes the specified message text, or object name, followed by a carriage return.

WriteLineIf (condition, message)

Writes the specified message text followed by a carriage return if the specified condition is true.

The behavior of these methods depends on the listener application chosen. For example, the Assert() method displays a message box when the default listener is specified.

Default Listener Application

The Trace class provides a Listeners collection that allows us to add a new listener application. When no new listener object is added to the collection, the Trace class uses the default listener application: the Output debug window. This window is provided by the Visual Studio .NET IDE during debugging. Let's see a simple example, TraceExample1:

    static void Main()    {      Trace.WriteLine("Entered Main()");      for (int i = 0; i < 6; i++)        Trace.WriteLine(i);      Trace.WriteLine("Exiting from Main()");    } 

The code is really simple; it writes tracing information when entering and exiting from the Main() method, plus the variable's values in the loop. In the next screenshot, you can see how the Visual Studio .NET Output listener shows the information:

click to expand

The Trace class also provides two useful methods to assert error notification: Assert() and Fail(). The former allows developer to check a condition provided as parameter and write a message into the listener when this condition is false. The latter writes a message into the listener each time a failure occurs. When no other listener is added to the collection, the Assert() method displays a message box to inform the user about an assertion failure. The following snippet of code, TraceAssert.cs, can be tested when the SQL Server service has been stopped deliberately in order to raise a connection exception:

    using System;    using System.Data;    using System.Data.SqlClient;    using System.Threading;    using System.Diagnostics;    namespace TraceAssert    {      class Class1      {        [STAThread]        static void Main(string[] args)        {          // Create a thread          Thread t;          t = new Thread(new ThreadStart(DBThread));          // Start the thread          t.Start();        }     private static void DBThread()     {       // Create a connection object       SqlConnection dbConn = new           SqlConnection("server=.;database=pubs;uid=sa;pwd=");       // Create a command object to execute a SQL statement       SqlCommand dbComm = new           SqlCommand("SELECT * FROM " + "authors", dbConn);       SqlDataReader dr = null;       Trace.WriteLine(DateTime.Now + " - Executing SQL statement");       try       {         // Open the connection to the database         dbConn.Open();         // Assert that the connection opened         Trace.Assert(dbConn.State == ConnectionState.Open,             "Error", "Connection failed...");         // Execute the SQL statement         dr = dbComm.ExecuteReader(CommandBehavior.CloseConnection);         // Assert that the statement executed OK         Trace.Assert(dr != null, "Error",             "The SqlDataReader is null!");         while (dr.Read())         {           // Reading records         }       }       catch       {         // Log the error to the Trace application         Trace.Fail("An error occurred in database access");       }       finally       {         if ((dr.IsClosed == false) && (dr!=null))           dr.Close();       }     }   } } 

In the Main() method, a new thread is created and started. The new thread runs the code within the DBThread() method. This code simply tries to contact the pubs SQL Server database, and retrieve all the data contained within the authors table. If the SQL Server service were not available, the following assertion failure would be displayed upon execution of the code:

click to expand

The row that raises that assertion is:

                Trace.Assert(dbConn.State == ConnectionState.Open,                    "Error", "Connection failed..."); 

As you can see, the first parameter checks whether the state of the connection is Open. It will be set to false when the connection has not been opened, so the assertion will be displayed. As you will see later in the chapter, you can deactivate tracing messaging using the application configuration file. In that way, you can decide whether or not to display assert messages at run time.

Using Different Listener Applications

In this section, we will see how to change the default listener application. The Trace class (and the Debug class as you will see later) exposes the Listeners collection, which contains a collection of listener applications. Without adding any new listener class, the DefaultTraceListener will point to the Output debug window provided by Visual Studio .NET. However, the .NET Framework provides another two classes that can be used as listener applications:

Class

Description

EventLogTraceListener

Using this class, you will redirect tracing messages to the Windows Event Log

TextWriterTraceListener

Using this class you will redirect tracing messages to a text file, or to a stream

In a multi-threaded application, you change the default listener with one of the listed listeners if you need to trace an application's behavior during its execution outside of Visual Studio .NET. Naturally, the Output debug window is available only during debug. Using these two classes, you could choose whether trace messages are placed in the Windows Event log, or inside a text file. Usually, when you know that your application will run in an operating system equipped with the Event Log, the EventLogTraceListener class is the best solution to choose. The reasons include:

  • The Event Log is managed by the operating system.

  • The Event Log allows administrators to specify security settings for the log.

  • The Event Log has to be read with the Event Viewer. This displays in a better visual environment than occurs with text file in Notepad.

Changing the default listener is simple, so let's see an example, TraceEventLog.cs:

    static void Main(string[] args)    {      // Create a trace listener for the event log.      EventLogTraceListener eltl = new EventLogTraceListener("TraceLog");      // Add the event log trace listener to the collection.      Trace.Listeners.Add(eltl);      // Write output to the event log.      Trace.WriteLine("Entered in Main()");    } 

Firstly, we have to create a new listener object. In the example above, a new EventLogTraceListener has been created in order to use the Windows event log as listener application. The class constructor accepts a string where we can specify the name of the source that has written an entry. The constructor will instantiate a new EventLog object assigning the specified source name to the Source property of the EventLog class, automatically.

The next step is adding the new listener object to the Listeners collection using the Add() method and providing the reference to the listener object. Finally, we can start to write tracing messages that will be redirected to the listener application.

Opening up the Windows event log using the Event Viewer application, you should see the new entry appearing in the Application Log section:

click to expand

You can double-click the item inside the Application Log report to examine the message:

click to expand

The code that we have examined above adds a new listener to the Listeners collection so that you will receive tracing messages both in the Output debug window, and in the event log. If you want to remove the default listener in order to use just the event log application, you have to call the RemoveAt() method, as illustrated in the code below:

    static void Main(string[] args)    {      // Create a trace listener for the event log.      EventLogTraceListener eltl = new EventLogTraceListener("TraceLog");      // Remove the default listener Trace.Listeners.RemoveAt(0);      // Add the event log trace listener to the collection. Trace.Listeners.Add(eltl);      // Write output to the event log. Trace.WriteLine("Entered in Main()");    } 

The TextWriterTraceListener Class

We are going to conclude our listener explanation by examining the TextWriterTraceListener class. It is useful when you have to write our tracing messages to a text file or directly in a console application. In fact, during the creation of a TextWriterTraceListener object, you can specify either a TextWriter object, or a Stream object. Using a Stream object allows you to specify more details on how the file stream is handled. The following snippet of code, TraceConsole.cs, shows how to trace messages in a Console application:

    static void Main(string[] args)    {      // Remove the default listener      Trace.Listeners.RemoveAt(0);      // Add a console listener      Trace.Listeners.Add(new TextWriterTraceListener(Console.Out));      // Write a trace message      Trace.WriteLine("Entered in Main()");    } 

Specifying the Console.Out streaming in the class's constructor, our Console application will display tracing messages:

click to expand

Finally, let's see how to add text log files as listener. We have to add a new TextWriterTraceListener object, specifying a FileStream object in its constructor. When the application ends, you have to use the static Close() method provided by the Trace class in order to close the log writing all the tracing messages. In the following code, Debugging.cs, a thread is started that traces both main and secondary thread messages:

    private static void WritingThread()    {      // Trace an info message      Trace.WriteLine(DateTime.Now + " - Entered WritingThread()");      // Sleeping for one sec....      Thread.CurrentThread.Sleep(1000);      // Trace an info message      Trace.WriteLine(DateTime.Now + " - Slept for 1 second...");    } 

The WritingThread() method is simply used by the thread to sleep for a second and write some tracing messages.

Here, we create a new FileStream object, either creating or opening the Debugging.log file, if it already exists. Then, we add the new listener into the Listeners collection by creating a new instance of the TextWriterTraceListener class within the Add() method:

    static void Main(string[] args)    {      // Create a file listener      FileStream fs = new          IO.FileStream("C:\Debugging.log", IO.FileMode.OpenOrCreate);      Trace.Listeners.Add(new TextWriterTraceListener(fs)); 

After starting the thread, the code waits for the carriage return key from the user and then closes the listener application and flushes all the tracing messages to the log file:

      // Write the line only when the switch is on      Trace.WriteLine(DateTime.Now + " - Entered Main()");      // Create a thread      Thread t;      t = new Thread(new ThreadStart(WritingThread));      // Start the thread      t.Start();      // Wait for the user carriage return      Console.Read();      // Close the file listener flushing the trace messages      Trace.Close();    } 

The output of the code will be something similar to this:

 30/04/2002 16:38:15 - Entered Main() 30/04/2002 16:38:15 - Entered into WritingThread() 30/04/2002 16:38:16 - Slept for one second 

The Trace class provides a useful property called IndentLevel for indenting tracing messages. For instance, you could use different indent levels for tracing messages written by the main and secondary threads. Adding the following lines to the code above, we can accomplish this task easily:

    private static void WritingThread()    {      // Setting indent level      Trace.IndentLevel = 2;      // Trace an info message      Trace.WriteLine(DateTime.Now + " - Entered WritingThread()");      // Sleeping for one sec....      Thread.CurrentThread.Sleep(1000);      // Trace an info message      Trace.WriteLine(DateTime.Now + " - Slept for 1 second...");    } 

The output of the modified code is:

 30/04/2002 16:40:07 - Entered Main()     30/04/2002 16:40:07 - Entered into WritingThread()     30/04/2002 16:40:08 - Slept for one second 

start sidebar

You can increment or decrement the level of the indentation using the Indent() and Unindent() methods, respectively.

end sidebar

Tracing Switches

When you are near to the application deployment phase, you will probably want to remove all the tracing and debugging messages from the code. However, you do not have to look for every trace instruction and remove it. You can use compilation flags during the application building. From the Visual Studio .NET IDE, you can right-click on the project name within the Solution Explorer window, selecting the Properties item from the context menu. The following dialog box will appear:

click to expand

You simply need to uncheck the Define DEBUG constant and Define TRACE constant checkboxes, recompile the solution, and all the Trace and Debug statements will be stripped from the application.

start sidebar

In order to remove tracing functionalities, you can even use the csc.exe command-line compiler. Simply use the /d:TRACE=FALSE /d:DEBUG=FALSE switches when compiling.

end sidebar

Adding switches to the traced code allows us to activate/deactivate tracing messages at run time. By simply declaring a value in the configuration file of our application, you can activate the trace functionality without rebuilding the entire solution. Naturally, you have to build the application to maintain tracing information, and this results in a greater final application size and slower performance, even when the switches are turned off.

The BooleanSwitch and TraceSwitch classes are provided by the .NET Framework to implement these switches. Let's first examine the BooleanSwitch class.

The BooleanSwitch Class

Using this class in the traced code, you can decide to activate/deactivate messages by simply changing a value in the application configuration file. The WriteLineIf() and WriteIf() methods will be useful to write messages depending on the Enabled property provided by the BooleanSwitch class. In order to add switches to your application you have to follow these few steps:

  1. Add an application configuration file either manually, or by selecting Add New Item... from the Project menu within Visual Studio .NET, and choosing the Text File template from the dialog box choosing the App.config filename.

  2. Open the configuration file in order to insert the necessary XML tags to inform the application about the switch name and value. Specifying a value equal to 0 will deactivate tracing functionality, and a value of 1 activate it:

     <?xml version="1.0" encoding="utf-8" ?> <configuration>   <system.diagnostics>     <switches>       <add name="MySwitch" value="1" />     </switches>   </system.diagnostics> </configuration> 

  3. Create a new BooleanSwitch object in the code that has the same name as that specified in the configuration file. You could also use the Enabled property in conjunction with the Trace static methods. Let's continue our Debugging example, by declaring a global BooleanSwitch object in order to use it everywhere in the code:

     BooleanSwitch bs; static void Main(string[] args) {   // Create a Boolean switch called MySwitch   bs = new BooleanSwitch("MySwitch",       "Enable/Disable tracing functionalities");   // Create a file listener   FileStream fs = new FileStream("C:\Debugging.log",       FileMode.OpenOrCreate);   Trace.Listeners.Add(new TextWriterTraceListener(fs));   // Write the line only when the switch is on   Trace.WriteLineIf(bs.Enabled, DateTime.Now +       " - Entered in Main()");    ... } 

In the Main() method we create the object, specifying the same name used in the configuration file plus a brief description. The WriteLineIf() method will write the message only if the Enabled property has been set to 1 in the configuration file.

The TraceSwitch Class

This class is an enhanced version of the BooleanSwitch class because it allows us to choose whether to deactivate tracing functionality or display messages using an importance-based hierarchy. The following table lists the levels:

Trace level

Description

0

None: Tracing is deactivated.

1

TraceError: Only the error messages will be written to the listener application.

2

TraceWarning: Error and warning messages will be written to the listener application.

3

TraceInformation: Error, warning, and information messages will be written to the listener application.

4

TraceVerbose: All kinds of messages will be written to the listener application.

So, when an error occurs, you can change the application configuration file, specifying to write just the error messages that you have added to the code to focus our attention just on these messages. The configuration file is the same as for the BooleanSwitch example. What changes is the code, as we have to instantiate an object from the TraceSwitch class. Moreover, we will use the enumeration values within the class to specify the level of the tracing messages. Let's see an example, TraceSwitchExample.cs:

    using System;    using System.Threading;    using System.IO;    using System.Data;    using System.Data.SqlClient;    using System.Diagnostics;    namespace TraceSwitchExample    {      class Class1      {        private static TraceSwitch ts;        [STAThread]        static void Main(string[] args)        {          // Create a boolean switch called MySwitch          ts = new TraceSwitch("MySwitch", "Four different trace levels");          // Create a file listener          FileStream fs = new FileStream(@"C:\Debugging.log",              FileMode.OpenOrCreate);          Trace.Listeners.Add(new TextWriterTraceListener(fs));          // Write the line only when the switch is set          // to TraceInformation level or above.          Trace.WriteLineIf(ts.TraceInfo, DateTime.Now +              " - Entered in Main()");          // Create a thread          Thread t;          t = new Thread(new ThreadStart(DBThread));          // Start the thread          t.Start();          // Wait for the user carriage return          Console.Read();          // Close the file listener flushing the trace messages          Trace.Close();        } 

We start by declaring a global TraceSwitch object and then create a new object, giving it the name specified in the configuration file. We add a text file log listener to the application. We then start a new thread that contacts the pubs database within SQL Server in order to retrieve all the records from the authors table.

If the thread has been omitted, the Open() method raises an exception that generates a trace error message:

    private static void DBThread()    {      // Trace an info message      Trace.WriteLineIf(ts.TraceInfo, DateTime.Now +                  " - Entered in DBThread()");      // Create a connection object      SqlConnection dbConn = new          SqlConnection("server=.;database=pubs;uid=sa;pwd=");      // Create a command object to execute a SQL statement      SqlCommand dbComm = new SqlCommand("SELECT * FROM authors", dbConn);      SqlDataReader dr = null;      try      {        Trace.WriteLineIf(ts.TraceInfo, DateTime.Now +                " - Executing SQL statement");        // Execute the SQL statement        dr = dbComm.ExecuteReader(CommandBehavior.CloseConnection);        while (dr.Read())        {          // Reading records        }      }      catch (Exception ex)      {        Trace.WriteLineIf(ts.TraceError, DateTime.Now +            " - Error: " + ex.Message);      }      finally      {        if ((dr.IsClosed == false) && (dr != null))          dr.Close();      }    } 

Here is the output from the code when the value 1 is specified in the configuration file, which specifies TraceError:

 19/04/2002 17:52:23 - Error: ExecuteReader requires an open and available Connection. The connection's current state is Closed. Slept for 1 second 

Here is the output when the value 3 is specified in the configuration file, which specifies TraceInformation:

 19/04/2002 17:54:23 - Entered Main() 19/04/2002 17:54:23 - Entered DBThread() 19/04/2002 17:54:24 - Executing SQL statement 19/04/2002 17:54:24 - Error: ExecuteReader requires an open and available Connection. The connection's current state is Closed. 

The Debug Class

The Debug class provides the same functionality as the Trace class. You will find that it exposes the same methods and properties, with the same tracing results.

start sidebar

When you change the listener application using the Listeners collection provided by the Trace class, you will change the listener application for Debug messages as well.

end sidebar

The big difference between these two classes is in the context in which you should use them. The Debug class is useful when you need to add information during debugging sessions. Before deploying our application, you will build the release version that will remove debug information from our code, automatically. Therefore, you would add Trace class functionalities when you need to check our application during the run time phase.



 < Day Day Up > 



C# Threading Handbook
C# Threading Handbook
ISBN: 1861008295
EAN: 2147483647
Year: 2003
Pages: 74

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