RSS Feed
Download our iPhone app
Browse DevX
Sign up for e-mail newsletters from DevX


Exploring Secrets of .NET Diagnostics : Page 5

Instrumenting an application with tracing has become increasingly sophisticated as the .NET framework has matured. Find out how to use tracing in your applications, how to fine-tune tracing to your needs with custom listeners, and how to gain field-level and robust formatting control over the output.

A Guide to Controlling Trace Output
As a summary to the vital concepts you have been reading about, Table 8 collects reference links that you may need. Don't remember the difference between a TraceSwitch and a TraceSource? Check this table. Need to know how to define something in the configuration file? Check this table!

Table 8: Key .NET Diagnostic Concepts: This table provides a brief description, plus links on the left to passages in the .NET Framework Developers Guide, and references on the right to the formal documentation.
Entity Description References
TraceSources Provides a set of methods and properties that enable applications to trace the execution of code and associate trace messages with their source. API, How To: Create and Initialize Trace Sources
TraceSwitches Provides a multilevel switch to control tracing and debug output without recompiling your code. API
TraceListeners Provides the abstract base class for the listeners that monitor trace and debug output. API

How to: Create and Initialize Trace Listeners

How to: Use TraceSource and Filters with Trace Listeners
TraceFilters Trace filters can be used by trace listeners to provide an extra layer of filtering beyond that provided by trace switches. API
Configuration File Schema Reference for the syntax of the portion of the app.config file related to diagnostics. Trace and Debug Settings Schema

Author's Note: Though I have not used it in the app.config file, for completeness the fifth and last component of controlling what you log here: set each Listener to the appropriate output level. Recall that you could set the level of the TraceSwitch for a TraceSource to control the amount of output to all TraceListeners operating under the auspices of that TraceSource. You can further refine that by setting each TraceListener's output individually with a child <filter> element. You will see an example of this in the next section as well.

Interested in designing your own TraceListeners? Take a look at the source code for the AlignedTextWriterTraceListener referenced in this project, or the TextBoxTraceListener discussed in the next section. A couple other tutorials specifically on customization are also available: CLR Inside Out: Customized Tracing and Custom Trace Listener.

Build a Trace Output Comparison Sandbox
Figure 4. Diagnostic Sandbox: This is the UI for the TraceSourceGuiApp that lets you experiment with any combination of source levels, trace options, and trace listeners.
It helps when you can actually compare the output of each TraceListener, using identical diagnostic output. The next application provides a sandbox tool so you may do this yourself. You'll also see snapshots of each TraceListener's output. Figure 4 shows the UI for the tool. The top half enumerates all source levels, trace options, and trace listeners. The bottom half contains a large TextBox used for user feedback; it's initially loaded with a dump of the main TraceSource used by the application. You can get a current snapshot at any time by pressing the Dump button.

All the radio buttons and check boxes are populated dynamically. The first two group boxes will probably not change much over time, but they may when a new release of the framework comes out. The third group box—the trace listeners—is populated based on the contents of the app.config file and/or any TraceListeners that you create programmatically. Each TraceListener gets a check box in the trace listener group, but its configuration will also be listed in the output area. This configuration reflects the initial configuration from the app.config file, plus any changes you have made programmatically before the form actually opens.

Given the preliminaries you learned in the earlier parts of this article, the user interface of this tool should be reasonably evident. If you want to refresh yourself:

  • Source Levels—see Table 4
  • Trace Options—see Table 6 (the shaded rows)
  • Trace Listeners—see Table 5 (except for the TextBox).
Figure 5. TraceListener Output: Each TraceListener includes a specification for where its output goes. The file names are detailed in the application's configuration file. If you want to change one, you can edit the configuration file without needing to recompile the application.
All the TraceListeners except the TextBox type are detailed in Table 5. The TextBox TraceListener exists in the sandbox project only; it allows you to send diagnostic output directly to the text box of this application.

Working with the application is simple: select a source level, any number of trace options, and any number of trace listeners, then press Go. The application then exercises each of the common trace event types (see Table 2) for all TraceListeners you have selected. The output of each trace listener goes to a different destination, as shown in Figure 5. Note that output from the Console TraceListener will not be available unless you are running inside Visual Studio, because the application is a graphical rather than a console application.

Table 9 shows the output from each TraceListener responding to the same trace events with the same output settings. You can reproduce equivalent results by selecting All for the source level; DateTime, ProcessId, and ThreadId for trace options (my preferred combination); and all the trace listeners listed. The more expansive formats have linked figures showing more complete samples. Note that there is no requirement for all TraceListeners to use either the same source level or the same output options; the TraceSourceGuiApp just happens to work that way for easier "apples to apples" comparisons.

Table 9. TraceListener output samples: Thumbnail samples of all TraceListeners are shown here; refer to the individual figure references indicated for larger samples of the more expansive formats.
TextWriter and Console output sample (see Figure 6):
DelimitedList output sample:
AlignedText and TextBox output sample:
XmlWriter output sample (see Figure 7):
Event Log output sample:

Figure 6: Full TextWriter and Console Output Sample: Selected trace options are output one per line.
Figure 7: Full XmlWriter Output Sample: Your diagnostic message maps to the <ApplicationData> element, and your selected trace options are emitted as XML attributes of the <Execution> element.
Figure 8: Application configuration file: The highlighted items are: (1) the two TraceSource names, which you use to reference the TraceSources from your code; (2) the output files passed to constructors of a couple TraceListeners; and (3) a filter that acts as a single-listener-specific switch.
For brevity, this article won't walk through the TraceSourceGuiApp code, but it will discuss the key points. First, take a look at the configuration file (see Figure 8). For educational purposes, the application uses two TraceSources. The previous application used one in the main program and another in an external library. Here, the main program uses both. Each uses the same set of seven listeners: six are defined in the configuration file as shown here and the remaining TextBox listener is defined programmatically.

Most TraceListeners have a variety of constructors, but the configuration file does not support all of them—it supports only those that take string arguments. As the configuration sample shows, you pass the string arguments to the constructors in the initializeData attribute. Typically, this will be a file name. That means that you can't initialize a constructor that uses a Stream, a TextBox, or any other object, in the configuration file. That does not necessarily prevent you from instantiating all your listeners in the configuration file. You could define a listener here using some dummy string arguments, and then update it during your program initialization to configure it completely.

Another new feature introduced here is the <filter> child attached to most of the listeners. It is not strictly necessary, because it's manipulated in the code, but including it here illustrates where it would be used. The combination of the source level of the switch and the source level of the filter determines the trace listener's ultimate output.

The earlier discussion of the TraceSourceDemoApp application mentioned that you should generally use shared listeners rather than private listeners if you want seamless integration for your separate modules. I stated that shared listeners use the same instance of a listener. That is only partly true. (I suspect it is a bug and should have been completely true.) Using a shared listener lets all consumers write to a single file. That's true. Using a shared listener lets you set the TraceOutputOptions once and everyone gets access to them. That's true. But then there's the common indentation issue. When different TraceSources are in the same file, such as in this TraceSourceGuiApp, they have common indentation. When they are in different modules, such as in the TraceSourceDemoApp, they do not have common indentation. As I was designing my custom AlignedTextWriterTraceListener I discovered the indentation was corrupted, so I had to build in a fix to make the indentation global. Table 10 summarizes the differences between shared and local (or private) TraceListeners.

Table 10: Shared vs. Local Listener Summary: The table summarizes the differences between shared and local listeners.
Shared Listener Local (Private) Listener
Common TraceOutputOptions Independent TraceOutputOptions
Common output file Separate output files
Independent indentation control (except for AlignedTextWriterTraceListener) Independent indentation control

The configuration file connects to your program by the names of the <source> elements as you have seen. In the TraceSourceGuiApp constructor you see where these TraceSources are initialized with names corresponding to the configuration names. This application is again using StructuredTraceSources instead of TraceSources to take advantage of the extra program flow tracing methods they offer. The constructor also builds a list of its TraceSources so that when a user chooses the settings, they may be applied to both TraceSources. The final statement sets up the TraceListener that is used to channel output into the output pane of the application. This TraceListener is defined in InitializeTextBoxListening, specifying the TextBox it will send output to and a name to reference it. The second line programmatically turns the listener off via its Filter property, the programmatic corollary to how you saw it done in the configuration file. The final line adds this listener to both TraceSources—just as in the configuration file, all the other listeners were added to both TraceSources.

   public TraceSourceGuiForm()
      AlignedTextWriterTraceListener.SourceNameLength = 16;
      tracer = new StructuredTraceSource("TraceSource_Main");
      tracer2 = new StructuredTraceSource("TraceSource_Secondary");
      sources = new TraceSource[] { tracer, tracer2 };
   private void InitializeTextBoxListening()
      TextBoxTraceListener l = new TextBoxTraceListener(
         diagTextBox, "TextBox");
      l.Filter = new EventTypeFilter(SourceLevels.Off);
      foreach (TraceSource source in sources) 
         { source.Listeners.Add(l); }
Pressing the Go button invokes the event handler shown below is invoked. This method first sets the source level for both TraceSources based on the user's selected settings. Next, it sets the TraceOutputOptions of each listener to the set of options the user chose. That method walks through the list of listeners, setting each one either on or off depending on the checked choices. The ExerciseTraces method simply invokes a series of TraceEvents. Finally, the method flushes the output so all listeners will write to their respective media for you to view.

   private void goButton_Click(object sender, EventArgs e)
      // Set the user's selected level.
      foreach (TraceSource source in sources)
      { source.Switch.Level = GetSourceLevel(sourceLevelGroupBox); }
      // Set the user's selected output options.
      foreach (TraceSource source in sources) 
         { source.Flush(); }

Close Icon
Thanks for your registration, follow us on our social networks to keep up-to-date