Introduction
This article was written as a part of a presentation for
Norwegian .NET User Group.
Some time ago in my
article about custom .NET trace listeners I presented various methods
to customize generation of .NET application trace output. Originally I planned
to update that article to include more advanced customization, but I changed my
mind due to several reasons:
-
Built-in .NET trace facilities have serious limitations that prevent .NET
Trace
class from being the best choice for production level tracing; because of these
limitations developers should customize standard Trace
only up to a certain level � above that level they should consider other
options;
-
New custom trace listener examples have been posted on the Web since I wrote my
article, including an SQL-based trace listener (explained in the article
Tracing in .NET and Implementing Your Own Trace Listeners at
15seconds.com), so I decided to use hyper-link capabilities instead
of writing my own version of the same thing;
-
Microsoft recently announced
their plans for future .NET development, and next version of VS.NET
(code-named �Everett�) will include Enterprise Instrumentation Framework that
will provide functionality currently missing in standard .NET trace.
So instead of writing new fancy trace listeners, I decided to investigate the
following questions:
-
Should release builds of .NET applications be compiled with
TRACE
option?
-
Does built-in .NET trace provides a good choice for trace output generation in
production environment?
-
What are alternatives to .NET
Trace
class?
Release builds: to trace or not to trace
This will of course be a matter of choice, although Microsoft has shown their
own preferences: TRACE
symbol is not defined for release build of
newly generated managed C++ projects, but release builds of C# projects have TRACE
defined. Looks confusing but I think it is reasonable: it is useful to have a
trace backdoor for final versions of your applications, and as long as there
are no active listeners (specified in the configuration file), there will be no
trace output. On the other hand, C++ language traditions does not have default
trace capabilities, so C++ developers accept that they should remember to
explicitly define TRACE
symbol for release builds of their
applications.
But there are different opinions on this matter. Richard Grimes in his book
Developing Applications with Visual Studio.NET criticized the idea of
enabling trace in release builds. He listed four arguments:
-
Trace messages are always generated (even when not collected), so it affects
performance;
-
Code that generates trace messages takes up space in a binary;
-
If customer uses his own version of a trace listener, and this version is badly
written, your application will suffer from the code it has nothing to do with;
-
Leaving trace messages in your release means that you have not fully tested
your code; your application should only suffer from exceptional situations
which should be reported to Windows Event Log.
I must say I fully agree only with the first of these statements: if your
application produces detailed output of its activities, it will always affect
performance. On the other hand, this must be a conscious choice: low level
routines that can be used within loops should probably limit its output to
debug builds, but if you need an activity report from an application in
production, you will just have to take performance cost.
I don't think trace messages add up so much to your binary size that you should
consider removing them just for this reason. Modern technology trend
does not care much about disk space consumption. Look at .NET with its XCOPY
deployment! And how much of your application CD will be taken by trace
messages? Just a small fraction.
The argument about custom trace listeners that can affect your application
execution requires your attention: yes, adding trace functionality adds a new
dimension to your application and sets certain requirements to trace consumers.
But this is a general concern that you have to take when shipping your program
to a customer: hardware, operating system service pack, device drivers � there
is a lot of parameters that you can�t control but must consider. Again, I don�t
think you should disable application trace just for this reason.
And finally, the argument that I most strongly oppose: leaving trace messages in
the release build indicates that you have not fully tested your code. Such
statement brings us back to the world of stand-alone applications running on
stand-alone machines, where if a program fails, it must be that particular
program�s responsibility. Modern computer programs aggregate so much of
external functionality that in case of failure it�s not often possible to point
a finger to a single module � system administrators must collect enough
information to trace back a sequence of steps that led to an error.
So my personal choice is to enhance release versions of applications with trace
capabilities, either by defining TRACE
symbol during compilation,
or by using custom reporting facilities. Take some precautions to limit the
size of your trace output (one approach was demonstrated in my previous
article), but give your users an option to generate program activity report.
Release builds: is .NET Trace
class a right choice?
Now that I have convinced you that you should have trace or some alternative
enabled in release builds (or at least I convinced myself one more time), let�s
see if existing .NET Trace
class can be used as a basis for trace
in a production environment. As with any .NET class, you can override default
trace functionality (by implementing custom listeners, not by subclassing Trace
class that is sealed). However, there are two major design limitations of
standard .NET Trace
that may force you looking for alternatives. I
described them in my previous article, but let me summarize it here:
-
Methods of
Trace
and TraceListener
have no way of
defining message severity level � you can use conditional methods, but
conditions that are tested using either BooleanSwitch
or TraceSwitch
are not forwarded to a listener, i.e. listeners are not able to categorize
incoming messages by its TraceSwitch
level. As a result, trace output will not contain information about error
level.
-
When using standard trace functionality there is no way of
assigning different trace listeners different severity thresholds. Trace output
is the same for all listeners, though you will hardly send to Windows Event Log
the same amount of information you will write into a text file.
I consider these omissions to be a design flaw: leaving message severity levels
out of .NET trace implementation puts a serious limit on usability of Trace
class in production environment. I can think about only one reason why Trace
was designed in such a way: internally Trace
class is almost a
clone of Debug
class � they share the same implementation and
differ only in preprocessor symbols and default listeners. I wish Trace
had more on its own and was more extensible.
But before you decide that you will implement your own trace facilities (or use
the ones I�ll mention in the next section), remember that once you replace
built-in .NET Trace
class, you no longer use unified trace output.
If your application consists of several components written by different
developers � or even vendors � you will risk ending up with several trace
outputs, generated by different trace engines.
So what criteria should be used to select trace implementation for release
builds? The choice is of course yours, but I would consider the following:
-
If you develop reusable components, you should not add dependencies on custom
trace implementation: use built-in .NET classes, they will be a least common
denominator;
-
For most of desktop applications built-in trace listeners (debug, text and
event log) provide sufficient number of options; if you�re not satisfied with
the output format, you can always customize it by subclassing either listener
itself or corresponding
Stream
-based class; so in this case you
should also try to stay with standard .NET Trace
class;
-
If you develop enterprise level applications, especially if your program should
meat 24x7 availability requirement, you will most likely need to optimize trace
output for clarity, so in case of failure you should be able to quickly locate
suspicious parts in the system log; in such case you will need not only to
filter trace output per listener, but also store severity level per message �
none of these options are available from built-in .NET trace classes, so you
will need to look for alternatives.
Trace for enterprises
So what is a replacement for built-in .NET Trace
class in case you
need more sensitive control over trace output? It seems that Microsoft is aware
of current .NET trace limitations and is going to help us reasonably soon: next
version of Visual Studio.NET (code-named "Everett") will include Enterprise
Instrumentation Framework (EIF) that will fill the gaps and provide a lot of
new features. EIF will have support for configurable event filtering and
severity levels, so developers should be able to write the following code:
TraceMessageEvent.Raise("My trace message");
ErrorMessageEvent.Raise("My error", 1, "CODE");
And of course SQL databases can be configured as trace event repository which
makes EIF a good choice for audit purposes.
While Enterprise Instrumentation Framework meets your tomorrow demands,
if you are more concerned about where you want to go today, you must
look somewhere else. One nice alternative is a solution
TrimaTrace developed by a Norwegian company
Trimanet.It is much more powerful than standard .NET trace
functionality, it can be used remotely via TCP and has very good performance
(more than 30,000 messages per second locally on a Pentium III machine with 1
GHz CPU). TrimaTrace has unbeatable affordability, because it�s free.
After I uploaded the first edition of this article, I received a reference to
another alternative - .NET implementation of the popular
log4j Java API providing flexible and arbitrarily granular control over
log management and configuration. This implementation is also free and is
hosted on SourceForge by NeoWorks.
Conclusion
Every modern design decision is complex: all simple questions are already
answered, and we are left with necessity to compare and compromise. Topics like
debugging options and formatting trace output seem to be unimportant in the
face of real problems we have to solve every day. However, don�t
oversimplify them: they are parts of the foundation of your development
environment, and it�s worth arranging them properly.
Resources
-
Vagif Abilov.
Writing custom .NET trace listeners.
-
Richard Grimes.
Developing Applications with Visual Studio.NET.
-
Mansoor Ahmed Siddiqui. Tracing
in .NET and Implementing Your Own Trace Listeners.
-
TrimaTrace,
a freeware .NET trace tool by Trimanet.
-
Log4Net, a
freeware .NET port of Java log API by NeoWorks.