Basic System.Diagnostics

The .NET framework has shipped with the System.Diagnostics namespace since version 1.0. My efforts to build a method context information gathering framework on the services of System.Diagnostics has brought me a deeper understanding of its classes and configuration settings. I will talk about my method context information gathering framework in a later post, but first I thought I would get us all on the same page on System.Diagnostics.


System.Diagnostics implements several classes that play a key role in outputting trace text from your application. Better understanding these classes will bring you insights in to how to extend the existing diagnostic framework in .NET or how to set up the configuration file to make full use of the out-of-the-box functionality.


TraceListener


The TraceListener class receives trace texts from the application and outputs it to a specific channel it was written for. There is a DefaultTraceListener class that outputs its text to the Win32 API OutputDebugString. But there is also an EventLogTraceListener that outputs its text to the windows event log. There is even an XmlWriterTraceListener that will output Xml to a stream. There are more listeners you can choose from and you can even write your own. Just derive your listener class from the abstract TraceListener base class and implement the abstract methods.


A TraceListener also maintains an optional Filter. This allows you to fine tune the type of information that a TraceListener actually outputs. For instance, you could put an EventTypeFilter on the EventLogTraceListener to only output Error-type traces to the windows event log.


TraceListener instances are held in a collection that all receive the same trace text to output. This means that the same trace text can be output on different channels (each channel is represented by a TraceListener) at the same time. This collection can live at the global/static Trace or Debug classes or at a TraceSource.


TraceSource


A TraceSource represents a configurable trace object that maintains its own set of TraceListeners. An associated TraceSwitch (discussed next) controls the trace level for this ‘scope’. Typically a TraceSource is configured in the .config. When the code instantiates a TraceSource with the same name it reads its settings from the .config file. This way you can control what portions of your application code will output trace text.


TraceSwitch


A TraceSwitch maintains a TraceLevel property that controls the importance of the trace texts passed to the TraceListeners. The usual Error, Warning, Info and Verbose are supported. Typical use is to configure the TraceSwitch in the .config file and when the code instantiates an instance using the same name it reads its settings from the .config file. Although you can use TraceSwitches standalone they are usually associated with a TraceSource (in config). It is also possible to write your own TraceSwitch.


Configuration Settings


A lot of System.Diagnostics functionality is driven by the .config file. Lets dive right in and look at the following configuration:


<configuration>
  <system.diagnostics>
    <sharedListeners>
      <!– Choose your trace output channels –>
      <add name=”Console” type=”System.Diagnostics.ConsoleTraceListener”
           initializeData=”false” />
      <!– Only Error traces will go to the Event Log –>
      <add name=”ErrorEventLog” type=”System.Diagnostics.EventLogTraceListener”
           initializeData=”Jacobi.Diagnostics.TestApp”>
        <filter type=”System.Diagnostics.EventTypeFilter” initializeData=”Error” />
      </add>
    </sharedListeners>
    <sources>
      <!– Configure a TraceSource for each class–>
      <source name=”Jacobi.Diagnostics.TestApp”>
        <listeners>
          <add name=”Console” />
          <add name=”ErrorEventLog” />
        </listeners>
      </source>
      <source name=”Jacobi.Diagnostics.TestApp.Class1″ >
        <listeners>
          <add name=”Console” />
          <add name=”ErrorEventLog” />
          <remove name=”Default” />
        </listeners>
      </source>
    </sources>
    <switches>
      <!– SourceSwitch settings for classes –>
      <add name=”Jacobi.Diagnostics.TestApp” value=”Error” />
      <add name=”Jacobi.Diagnostics.TestApp.Class1″ value=”All” />
    </switches>
  </system.diagnostics>
</configuration>

The configuration settings live inside the <system.diagnostics> element. Then we see a <sharedListeners> section. Although it is possible to configure all TraceListener settings separately for each TraceSource, I prefer to maintain a global list of (configured) TraceListeners and refer to them from the TraceSource configuration. <sharedListeners> is that global place. Notice that the EventLogTraceListener has a <filter> defined that only allows Error-type traces to pass to the event log.


The <sources> section allows you to list the configuration settings for all the TraceSources your application uses. If the configuration for an instantiated TraceSource is not found in the .config file, it is shut off by default. So if you expect to see trace output from a specific TraceSource but there isn’t any, 9 out of 10 times you did not configure it right (check the spelling).


Each <source> declares its own collection of TraceListeners, in this case referring to one declared in <sharedListeners>. As a convention I’ve used the full class names as TraceSource names and TraceSwitch names. But you can also choose a courser granularity, say at component level or at sub-system level.


You can associate a TraceSwitch with a TraceSource by using the switchName and switchType attributes on the <source> element. I’ve not done so in my example and that means that you have to instantiate the TraceSwitches manually in code (with the correct name). You can associate a TraceSwitch with a TraceSource by either using the switchName and switchType attributes on the <source> element -or- by just declaring a switch (<switches><add>) with the same name as the TraceSource.


Wrapping up


This quick tour around System.Diagnostics discussed the main classes that enable you to build pretty powerful tracing support into your application. With this information you could already instantiate a TraceSource for each class and configure a matching TraceSwitch. Code inside the class would simply call the TraceSource and it would work. You could configure it to allow specific (type of) information to come through while the rest is blocked, for instance. And although I would encourage anybody to at least take some time to fiddle with a simple console test application to try out these features, it is my experience that you’ll want more in a real application. That is why I build my method context information gathering framework. Although this framework does not add much to the tracing capabilities of System.Diagnostics, it does add a lot to the quality of information that is in the trace texts.


I plan to write about my framework in a future post.


For more information on System.Diagnostics go to MSDN.