iagnostics, also known as "program tracing," is a crucial—and often overlooked and underappreciated—component of application development. I have long been an ardent believer in diagnostic support, and have written diagnostic systems from scratch for a diverse assortment of software and hardware products, including integrated circuit design tools, laser printers, and user interfaces. As Microsoft's .NET Framework Developer's Guide
puts it, diagnostics is:
a way to monitor the execution of your application while it is running. You can add tracing and debugging instrumentation to your .NET application when you develop it, and you can use that instrumentation both while you are developing the application and after you have deployed it.
[Y]ou can record information about errors and application execution to logs, text files, or other devices for later analysis."
What You'll Cover
Despite a plethora of diagnostic information on MSDN and elsewhere, you'll get some material from this article that's difficult or impossible to find anywhere else, such as:
- A practical example of multi-module instrumentation: You'll see how to design a library that you could use both with diagnostic-aware or with diagnostic-unaware programs.
- A sandbox: You'll find the sandbox a good place to exercise all combinations of diagnostic levels, output options, and output channels (listeners).
- Output examples: You can see at a glance the differences in output for every pre-defined listener.
- Comparison charts: The charts let you compare the output of each pre-defined listener: always, never, or on-demand.
- A customized listener: This listener lets you review a diagnostic log quickly and effectively. It functions as both a tutorial, giving you a step-by-step guide to learning and using diagnostics, and a reference—a summary of the salient details, pitfalls, and features you need to know.
The .NET framework has included diagnostic capabilities from its early days. Prior to .NET 2.0, the prescribed technique was to use the Trace or Debug classes. (These two classes have identical functionality; the only difference is that the framework compiles Trace functionality into release builds by default, but does not compile Debug functionality by default.) Both classes provide static methods, including WriteLine
, and others that interact with BooleanSwitch or TraceSwitch classes to provide diagnostic logging capabilities. Microsoft's Introduction to Instrumentation and Tracing
covers this older diagnostic capability thoroughly, so I won't cover it here.
||Both this reference and the earlier link are good starting points for tracing. An easy point of confusion, however, is that both of them focus primarily (but not entirely) on the old-style, pre-.NET 2.0 tracing. They hyperlink to TraceSources—a new style available from .NET 2.0 onward. Interestingly, I could not find an overview section in the MSDN documentation for the new-style tracing.
New-style tracing still relies in part on the old-style components. For example, to turn on automatic flushing, you need to set the static Trace.AutoFlush
property, as discussed later. See the "Tracing References
" sidebar for more links to pre-.NET 2.0 tracing. If you plan to follow along using the downloadable code
for this article, you should also take a look at the sidebar "The Demo Projects and Their Library Components
Using Diagnostics in Your Applications
|Figure 1. Demo Application: You can use this simple UI to explore many of the diagnostic capabilities built into the .NET framework.|
shows the demo application's simple user interface. When you launch the application, the text box initially contains a dump of the tracing settings specified in the application's configuration file. The check box represents the collection of settings you might activate in a typical application. The Go button begins a processing run, which generates some diagnostic output. The only action you will see on-screen is a message that you have pressed the button, plus another dump of the tracing settings. The code is in the event handlers attached to the button and the check box.
Instead of first discussing all the details about the diagnostic system mechanisms, I'll start with the sample application itself. Figure 2
shows diagnostic output from the application rendered by a myriad of choices that determine the quantity and type of output desired. The application records output to a log file (diagnostic.log
) in the same directory as the executable.
|Figure 2. File Tail Viewer: The diagnostic output in this file includes: (1) The start of a session; (2) The instantiation of a tracing object; (3) Entering an event handler method; (4) Entering a general method; (5) A constant message; (6) A message showing a parameter value.|
You can see two groups of properties in Figure 2
; the properties in this first group are a function of the TraceListener you choose. The.NET framework has five built-in TraceListeners to pick from, but I ended up designing my own because none of the standard TraceListeners meets all these goals:
- Strict adherence to one line per log event
- Date, time, source, process, and thread information attached to each record and always aligned vertically
- Indentation to visually show nesting of method calls such that all trace sources honor the same indentation
- Full control over what fields the output will include
The goals in this second group are a function of the mechanism for viewing the diagnostic output after it has been recorded by your application:
- On-the-fly color-coding capability
- Dynamic output, so every diagnostic record is displayed as soon as it is recorded
- Multiple logs are available if needed, via separate tabs in the diagnostic viewer
The screen shot in Figure 2
shows a popular, general purpose file-tail viewer called BareTail
a graphical implementation of the common Unix/Linux tail program. BareTail waits for more output to be written to a file by some external application, and then displays it immediately, which fulfills one of the goals listed above. While any good tail program will work, BareTail is nice because it includes a multiple-tabbed interface, provides all the color-coding you see in the figure (you simply need to specify the textual patterns you want to match and assign foreground/background colors for those matches)—and it's free.