Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Tracing Code: Part 2


The Trace and Debug classes are used for two purposes: to generate trace messages and to perform assertions. A trace message is some diagnostic message that you will use to debug your code; they are intended to be collected, collated, and viewed later as a collection. An assertion is more immediate—it performs a test on a condition and if that test fails, the assertion code generates a message that can be stored for later analysis; but more likely, the code will demand immediate action through a modal dialog. It makes sense that assertions should only exist in your code in debug builds because you do not want your customers to get a dialog telling them that not only has your code failed, but also that you thought that it may fail. I would also argue that trace messages should also only be used in debug builds, but I will return to this issue later.

The Trace and Debug classes have the same methods and these are marked with the [Conditional] attribute; the Trace methods have TRACE as the parameter of the attribute, the Debug methods have DEBUG as the parameter. This attribute is a hint to a compiler that it is not used by the runtime. When a compiler sees a method with the [Conditional] attribute, it should check to see if the specified symbol is defined, and if not, the calls to the method should be ignored. The C# compiler does this, but the C++ compiler does not. If you use C++, you have to use conditional compilation, but note that Debug builds define the _DEBUG symbol, not the DEBUG symbol.

The VS.NET generated C# projects will define DEBUG for Debug builds and TRACE for Debug and Release builds. The idea is that you can call the methods on Trace in a Release build, but this causes many problems. For a start, Trace has an Assert() method, but as I mentioned earlier this should never be called in Release builds. There is an argument that trace messages are useful in Release builds. The argument is this: If an application fails then the customer could be asked to monitor trace messages and send them to customer support for analysis. The problem with this approach is that the trace messages that are generated are handled by a trace listener class and the default listener (DefaultTraceListener) will generate the messages with the Win32 ::OutputDebugString() function.

::OutputDebugString() works by raising a structured exception 0x40010006. This is caught by the system and is delivered to monitoring processes via a shared memory section. Because this memory section is shared, access to it has to be synchronized. This is done using two named Win32 events: One is used to indicate that a monitor is reading the memory and hence prevents writing to it (DBWIN_BUFFER_READY), and the other is used by the system to indicate that a new message has been written to the shared memory (DBWIN_DATA_READY). A thread that calls ::OutputDebugString() is blocked until the DBWIN_BUFFER_READY event is signalled, this means that the monitor process is coupled to the process generating the messages, and a poorly written monitor process can perform unwanted synchronization on the process generating messages.

For this reason it is important that you only generate trace messages when you really do need to perform diagnostics. Because of this, the Trace and Debug classes have methods that perform a test, WriteIf() and WriteLineIf(); and you can use some mechanism like a command-line switch or a switch in a configuration file to indicate that traces should be generated. However, the code to generate the trace messages are in your code, and if you have many such calls, it means that you will have many conditional tests being performedclearly a performance problem.

As I mentioned in the last article, the trace messages are actually handled by a trace listener object that will decide how to handle the message. By default, youll get an instance of the DefaultTraceListener class, but you can use the configuration file, or in code the Trace::Listeners property, to remove this object. I dislike using the configuration file to do this because a user can undo your changes. I also dislike doing this through code because it means that you are overriding the process configuration file and thus have to use some other mechanism if you really do want to perform tracing.

The coupling problem occurs because of the design of the DefaultTraceListener class. You can partially solve this issue by overriding this class to generate the trace message on a thread pool thread; the download illustrates this. However, in my opinion, the best solution is that you should never make calls to generate trace messages in Release builds, so C# developers should not define the TRACE symbol. Trace messages should only be generated in Debug builds, and if your customer reports a problem with your code, you should send the Debug build to them. The most important piece of software that you use to develop .NET code takes this approach: Windows comes in two versions, the Free build (Release) and Checked build (Debug). Most people use the Free build, but if you want more diagnostics, you can install the Checked build.

The final problem I want to point out is information overload. The various methods to generate trace messages allow you to generate a trace message with a category string, but the current implementation merely concatenates this to the trace message, which is quite useless. Instead, the trace listener should have been designed to take a trace level number and only generate the message if the trace level is less than or equal to a configurable value. This would allow users to set a level at runtime. ATL7 does this exceptionally well; it is a pity that the .NET diagnostics designers did not consult their colleagues on the ATL team.


Richard Grimes speaks at conferences and writes extensively on .NET, COM, and COM+. He is the author of Developing Applications with Visual Studio .NET (Addison-Wesley, 2002). If you have comments about this topic, Richard can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.