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


Exploring Secrets of .NET Diagnostics : Page 4

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.

Controlling Trace Format
You have seen techniques for controlling the flow of trace information, but you also have substantial control over the format of that information with TraceListener objects. The app.config file code in Listing 1 adds a single TraceListener for each TraceSource. Both TraceListeners are of the same type—AlignedTextWriterTraceListener—a custom listener described in detail later. Both TraceSources use not only the same type of TraceListener, but by design they reference the same instance of AlignedTextWriterTraceListener. That's important for seamless integration among the collection of TraceSources. You could create a local TraceListener connection for a TraceSource by modifying the <add> child in the <listener> element. Instead of referencing a TraceListener from the <sharedListener> collection, you would explicitly define it in the <add> element. But each must reference its own file.

Author's Note: You cannot have different TraceListeners (or different instances of a single TraceListener) referring to the same file. Whichever TraceSource accesses the actual diagnostic file first receives a resource lock on it. Any other TraceListeners referring to the same file will silently fail in their attempts to write to it.

I find AlignedTextWriterTraceListener the most useful type for my development style, but you may prefer any of the several standard types, or create one of your own. The point is that you have great flexibility as to how the information is presented. This is the third component of controlling what you log: select (or create) a TraceListener that produces output most comfortable to your working style. Table 5 enumerates the available TraceListeners.

Table 5: TraceListener choices: How you wish to consume the diagnostic output will influence the type of TraceListener you choose.
TraceListener Details
  • Outputs to: Standard output or standard error stream (including Output window in Visual Studio)
  • Format: each attribute on a separate line (same as TextWriterTraceListener)
  • App.config parameter: none
  • Outputs to: Any TextWriter or Stream class (including console or a file)
  • Format: each attribute on a separate line
  • App.config parameter: filename
  • Outputs to: Any TextWriter or Stream class (including console or a file)
  • Format: all attributes start on the same line, but some will span multiple lines (e.g. call stack)
  • App.config parameter: filename
  • Outputs to: Any TextWriter or Stream class (including console or a file)
  • Format: XML-encoded data (but not valid XML by itself)
  • App.config parameter: filename
  • Outputs to: system event log
  • Format: standard event log format
  • App.config parameter: event category
  • Outputs to: Output window in Visual Studio, standard output, and message box
  • Format: each attribute on a separate line (same as TextWriterTraceListener)
  • Outputs to: Any TextWriter or Stream class (including console or a file)
  • Format: all attributes contained on a single line
  • App.config parameter: filename

Author's Note: See Trace Listeners in the .NET Framework Developer's Guide for further details. The shaded last row in Table 5 is a customized TraceListener; you can find more details in the documented API.

Footnotes to Table 5:

  • 1 The DelimitedListTraceListener emits standard comma-separated values (CSV file), wrapping string arguments in quotes. (It will use whatever delimiter you specify, not just commas.) So the natural question to ask is: what are the fields in the file? I was unable to find the answer to this in the MSDN library so I found it through experimentation. The fifth field was always empty in my trials so I could not say what it is for, but here's the list: Name, Level, EventId, Message, ?Reserved (empty) field, ProcessId, LogicalOperationStack, ThreadId, DateTime, TimeStamp, and CallStack. Note that an event may or may not be contained entirely on one line. If you include the CallStack, for example, though contained within quotes that field will include embedded line breaks.
  • 2 Through no fault of the XmlWriterTraceListener, it cannot create an output file that is legitimate XML at any instant. Why? A valid XML file consists of a single root element containing zero or more child elements. If the trace listener emitted the opening tag of the root, and then began emitting children, it would always be missing the closing tag of the root, so it would not be balanced and hence not valid. If instead—as it actually does—it ignored the root and simply emitted a list of children, one <E2ETraceEvent> element per event, then the file would be balanced but not valid because it would violate the rule of only one root (each child would in fact be considered a root in this case). So if you need to legitimize the XML you must wrap it in a root element. Alternatively, you can view it in an XML editor that is more forgiving.
  • 3 I list the DefaultTraceListener for completeness, but in practice its use seems to be rare. One important reason for this is that the Fail method pops up a message box that may not be expected or desired. If you use libraries written by others, you have no way of knowing whether that would occur. In every example and tutorial I have seen, the first thing they tell you to do is take out the default listener, either in the configuration file or in code.
Depending on what type of TraceListener you choose, some information fits more naturally than others. For example, I did not design the AlignedTextWriterTraceListener to handle the call stack as that is, by its nature, a voluminous multi-line construct, and one of the main strengths of AlignedTextWriterTraceListener is "one event = one line of output." Table 6 shows all the possible attributes for each standard TraceListener plus the AlignedTextWriterTraceListener. The shaded rows indicate those attributes that may be enabled or disabled via the TraceOutputOptions property of the listener.

The EventLogTraceListener does not use the TraceOutputOptions property at all, because according to Microsoft "it can cause a large volume of data to be written to the log." Thus, Table 6 shows that all entries for this listener are either "always" or "never."

Table 6: TraceListener Attribute Choices: Your choice of TraceListener determines your content possibilities. You can select the shaded attributes by setting the standard TraceOutputOptions.
Attribute AlignedText Console TextWriter DelimitedList XmlWriter EventLog
Indent On demand1 On demand2 On demand2 On demand2 Never Never
TraceSourceName On demand3 Always Always Always Always Always
TraceEventType On demand3 Always Always Always Always Always
EventId On demand3 Always Always Always Always Always
Message Always Always Always Always Always Always
ComputerName Never Never Never Never Always Always
LogicalOperationStack Never On demand On demand On demand On demand Never
DateStamp On demand On demand On demand On demand Always Always
TimeStamp On demand On demand On demand On demand On demand Never
ProcessName Never Never Never Never Always Never
ProcessId On demand On demand On demand On demand Always Never
ThreadId On demand On demand On demand On demand Always Never
Callstack Never On demand On demand On demand On demand Never

Footnotes to Table 6:

  • 1 Indent applies globally: any connected TraceSources will both honor and affect the current indentation.
  • 2 Indent applies locally: the indentation of each TraceSource is independent of any others in different executables (DLLs).
  • 3 The item is not selectable in the standard TraceOutputOptions but may be suppressed by setting the appropriate length property.
Your choice of TraceListener may be driven by what attributes you need to see in your diagnostic output; Table 6 can help you determine which TraceListeners will meet your needs. You could take the minimalist approach and show only the logging message that you pass in, or you could add any or all of the attributes listed in the table. This is the fourth component of controlling what you log: pick and choose the attributes you want in your output.

You can enable or disable the shaded entries in Table 6 for any TraceListener by combining elements of the TraceOptions enumeration into the TraceOutputOptions property of a TraceListener. Several other entries in the table are marked to indicate that you may enable or disable them solely for the AlignedTextWriterTraceListener—via a different mechanism. This custom listener provides properties that specify the output column width for each attribute. These properties are named with a "Length" suffix, as in ProcessIdLength. Setting this property to zero turns off the attribute. I felt it was useful to give length properties to all the fields where it made sense, so some of them have dual controls as a side effect—in other words you can turn off ProcessID either by omitting it from the TraceOutputOptions or by setting ProcessIdLength to zero. Conversely, to turn it on you must include it in TraceOutputOptions and ProcessIdLength must be a positive number. Table 7 summarizes the default settings for all the attributes of Table 6. The DateStamp attribute in Table 7 is unique in that it does not have a length property but instead has a format property, which defaults to MM/dd/yy hh:mm:ss.fff tt. See Standard DateTime Format Strings for how to specify date formatting.

Table 7: Default Field sizes for AlignedTextWriterTraceListener: You adjust the width of the bold attributes using static properties. The shaded rows delineate attributes selectable via TraceOutputOptions.
Attribute Default Length Property Name
Indent NA  
TraceSourceName 10 TraceSourceNameLength
TraceEventType 11 TraceEventTypeLength
EventId 5 EventIdLength
Message NA  
ComputerName NA  
LogicalOperationStack NA  
DateStamp 24*  
TimeStamp 8 TimestampLength
ProcessName NA  
ProcessId 8 ProcessIdLength
ThreadId 8 ThreadIdLength
Callstack NA  

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