
Introduction
This article shows how you can create a subclass of System.Diagnostics.TraceListener
which writes trace/debug information to an XML file. This is useful if you want to, for example:
- Read the resultant XML into a
DataSet
and then view it in a grid. Perhaps you might bind the grid to a DataView
which only shows a certain subset of the output messages (e.g.: you might only want to see the messages that were logged between midnight and 1:00 a.m.).
- Perform XPath queries on the XML data.
- Save the results of debugging sessions in a structured format for later use. Perhaps you might store the XML in a database so that the output of multiple debugging sessions can be retrieved and compared easily.
Background
The .NET Framework offers ample support for debugging and monitoring applications. The System.Diagnostics
namespace contains flexible classes which can be easily incorporated into any application. Within the System.Diagnostics
namespace lives an abstract class named TraceListener
. TraceListener
-derived classes are used when you call, for instance, Debug.WriteLine(...)
or Trace.Write(...)
to direct the output messages to some backing store (the default listener targets the Output window in Visual Studio and Assertion Failed message boxes). It is possible to create your own TraceListener
which can write out your messages to any destination you can think of (within the realm of reason :).
This article shows how to subclass TraceListener
and direct the debug messages to an XML file. This is accomplished via an XmlTextWriter
, which remains open throughout the duration of the object's existence. Please note that you must call Dispose()
on the XmlTraceListener
object when you are finished using it so that it will close the stream it uses. Once the object has been disposed of, it's probably a good idea to remove it from the Listeners
collection of Trace
and Debug
. Keep in mind that those two classes share the same ListenerCollection
, so removing it from one of them will remove it from both.
Each output message is stored in a separate XML element. All of the data is contained in attributes within the element. By default, the XmlTraceListener
does not add in too much extra information to each XML element. Since everyone's requirements will be different, the provided implementation just provides some basic data. If you find a need to write out extra attributes containing data you need, modify the WriteElement
method in XmlTraceListener
.
Using the code
Using the code is pretty simple. The attached WinForms sample project shows exactly how to do it. Of course, you can use the XmlTraceListener
in any type of .NET application. If you are working in low-trust environments, you might need to consider whether the machine on which the process that the XmlTraceListener
is running in will grant your code the requisite FileIOPermission
. Below are some relevant fragments which show how to use this class:
XmlTraceListener xmlListener;
private void XmlTracerForm_Load(object sender, System.EventArgs e)
{
this.xmlListener = new System.Diagnostics.XmlTraceListener( "debug.xml" );
Debug.Listeners.Add( this.xmlListener );
Debug.Write( "Form has loaded", "Marker" );
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if( this.xmlListener != null )
{
this.xmlListener.Dispose();
Debug.Listeners.Remove( this.xmlListener );
this.xmlListener = null;
}
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
The code in the XmlTraceListener
class is simple. The heart of this class is in the WriteElement
method. All of the output methods of TraceListener
were overridden and they simply pass the call off to this method.
private void WriteElement( string message, string extraInfo, bool isFailure )
{
this.xmlWriter.WriteStartElement( "output" );
this.xmlWriter.WriteAttributeString( "failure", isFailure.ToString() );
this.xmlWriter.WriteAttributeString( "time",
DateTime.Now.TimeOfDay.ToString() );
if( isFailure )
{
this.xmlWriter.WriteAttributeString( "message", message );
this.xmlWriter.WriteAttributeString( "detailMessage", extraInfo );
}
else
{
this.xmlWriter.WriteAttributeString( "category", extraInfo );
this.xmlWriter.WriteAttributeString( "message", message );
}
this.xmlWriter.WriteEndElement();
this.xmlWriter.Flush();
}
The XML output is straightforward. The output used to populate the DataGrid
seen at the top of the article is listed below. Besides for the sake of simplicity, the XML has elements with attributes only (as opposed to nested child elements) because this provides the most natural mapping to a DataTable
in a DataSet
.
="1.0" ="Windows-1252"
<traceOutput>
<output failure="False" time="18:25:01.4843750"
category="Marker" message="Form has loaded" />
<output failure="False" time="18:25:03.2343750"
category="TextChanged In TextBox" message="T" />
<output failure="False" time="18:25:03.4843750"
category="TextChanged In TextBox" message="Te" />
<output failure="False" time="18:25:03.7187500"
category="TextChanged In TextBox" message="Tes" />
<output failure="False" time="18:25:04.2187500"
category="TextChanged In TextBox" message="Test" />
<output failure="False" time="18:25:05.2187500"
category="TextChanged In TextBox" message="Testi" />
<output failure="False" time="18:25:05.3906250"
category="TextChanged In TextBox" message="Testin" />
<output failure="False" time="18:25:05.5625000"
category="TextChanged In TextBox" message="Testing" />
<output failure="False" time="18:25:07.0156250"
category="Marker" message="AddToList Button Clicked" />
<output failure="False" time="18:25:08.1406250"
category="SelectedIndexChanged In ListBox" message="0" />
<output failure="False" time="18:25:09.7968750"
category="Marker" message="Form Is Closing" />
</traceOutput>
Points of Interest
I hope that this little utility helps you shave some time off of your debugging sessions. Once you have the debug output in XML format, the possibilities of how you can view/filter/sort/compare/etc. it are endless. I also hope that if you devise a clever trick or technique using this utility that you'll take the time to share it with us!
History
- 4/3/05 - Created article.