Maintaining logs during development can help you quickly analyze problems pre- and post-delivery.
Even the best-designed software doesn't always perform as well in the
field as it may on your development machine. Collecting information is
critical when the customer is having a problem. In this column, I'll look
at some of the strategies for collecting and analyzing runtime information
both before and after you deploy your applications.
Logging During Development
When you're developing software you have the comparative luxury of dropping
into the debugger when something goes wrong. This means that you can usually
analyze any crash based on the actual state of the application, rather
than whatever history information you might have recorded. But there are
still some circumstances where maintaining a log file during development
can be very helpful:
- If you have colleagues helping you to test the application, a log
file can help you understand their actions when they do manage to provoke
- If the application depends on asynchronous input (such as financial
data from an external Web service, or events from an analog-to-digital
converter), a log can help clarify the order of events.
- If a bug is extremely rare, happening only after hundreds or thousands
of calls to a particular function, a log can let you search for patterns
after automated testing.
- Some bugs can't be tracked in the debugger because the very act of
going into debug mode changes the state of the application. Bugs involving
the focus of controls on the screen often fall into this category.
The main goal of logging during development is to collect information
that you can't get by running the code in the debugger. If you're logging
during development, you usually want the logging to be turned on full-time,
because you won't generally be concerned about too many log files cluttering
up your hard drive or a minor loss of performance. It's far better to
collect too much information than too little when you're hunting bugs.
Logging After Development
Logging grows in importance after you've shipped your application.
That's because the chance of getting back a good bug report goes way down
after the code leaves the hands of the customers and lands on those of
end users, who may have never had to write a bug report in their life.
After a few rounds of "it just crashed" and "I didn't do
anything special" you'll learn how useful a log file can be. Giving
users the ability to send you a log file lets them give you more information
with less effort—a definite win for both of you.
On the other hand, end users do care about performance, even if you don't.
If you collect a lot of information in a log file, you can add substantial
overhead to your application, as well as a substantial load to the user's
hard drive. This leads to a simple rule: In a shipping application, it
should be easy for the end user to turn logging on or off.
There are many ways to implement this switch for end users, including:
- Use a menu item or button in an out-of-the-way spot, such as the About
- Save a flag in a registry key. The easiest way to do this for end
users is to give them reg files to turn the flag on and off.
- Use an XML file that can be edited to turn logging on an off. In this
case, you'll want to include instructional comments in the file.
The point of any of these schemes is to let the logging code remain hidden
in your application in a dormant state until it's needed. That way, it
doesn't affect the performance of the rest of your code. If a problem
turns up, you (or if you're lucky, your support staff) can walk the end
user through the steps needed turn on logging. They can then recreate
the problem, send you the log file and turn logging back off.
What Should You Log?
The next question is what you should put in the log file. Remember,
the log file is supposed to be a diagnostic tool. You should save enough
information to let you tell what went wrong. A few things that might make
sense to save, depending on your application, include:
- Error messages and information, including a stack trace of the error.
- The state of internal data structures at key points in the application.
- User actions, such as button clicks and menu item selections.
- The time that significant actions were performed.
- Important information about the environment, such as variable settings
and available memory.
If you think about how you debug your application when it's running on
your computer, you should be able to come up with a good list of important
information to log. The simplest rule of thumb is to log the information
that you use when debugging interactively.
Logging for .NET
If you've been reading my column for any length of time, you know
I'm spending most of my coding day in the .NET universe now. So I'm going
to round out this column by listing some of the alternatives you can use
for logging in .NET. This isn't an exhaustive list; there are undoubtedly
.NET products that I'm unaware of, and there are many, many products for
other environments. But it will, I hope, serve to give you some ideas
of how you can implement logging.
For starters, you can simply use the System.Diagnostics.Trace and System.Diagnostics.Debug
classes from the .NET Framework. These classes let you send string output
to an array of TraceListeners, which can include disk files. There's built-in
support for configuring the tracing level via switches in an XML configuration
file, which can be handy.
There's another logging option in the System.Diagnostics namespace: You
can use the EventLog class to write directly to the Windows event logs.
This has the advantage that most users already know how to use the Event
Log Viewer to look at event log entries. It also puts your logging messages
into a repository that's not likely to be accidentally damaged or deleted
accidentally. Keep in mind, though, that there is no event log on Windows
95 or Windows 98, so you'll need to find a different solution if your
target market includes those operating systems.
If the basic facilities provided in the System.Diagnostics namespace
aren't sufficient, you can turn to some more comprehensive tracing software
from Microsoft. Start with the Enterprise Information Framework (EIF),
which you can download from www.microsoft.com/downloads/details.aspx?
EIF is designed to manage tracing for applications that include more than
one component. Its features include:
- A single model for raising and recording tracing and diagnostic events
across all parts of a distributed application
- Windows Management Instrumentation (WMI) compatibility for integration
with existing enterprise monitoring tools
- A flexible runtime configuration layer that's that's designed to be
modified by simple scripting code for "on the fly" changes
to your monitoring configuration
- Windows Event Tracing, a new service for high-speed, kernel-mode tracing
that's capable of recording hundreds or thousands of events in rapid
- Event correlation across distributed applications
EIF is good for distributed tracing, but it still lacks some features
such as levels of tracing. You can go that next step by downloading the
Logging Application Block from www.microsoft.com/downloads/details.aspx?
The Logging Application Block adds a bunch of features to EIF, including:
- Support for a set of logging levels similar to those that the Trace
- Asynchronous logging via MSMQ
- Logging to a SQL Server database.
- An event transformation engine that can add information to events
(for instance, common settings that apply to all events in a class),
delete unwanted information, and flexibly format the results using Extensible
Stylesheet Language Transformations (XSLT). There's also an interface
to that allows you to write your own formatting classes.
Of course, you're not limited to Microsoft when searching for logging
software. The best alternative I know of is log4net, an open-source project
available from http://logging.apache.org/log4net
(and based on a popular Java logging package). It has a ton of features,
- Support for the Microsoft .NET Framework 1.0 and 1.1, as well as the
.NET Compact Framework, Microsoft's shared-source implementation ("Rotor"),
and the open-source Mono implementation.
- Output to an extremely wide variety of targets, including databases,
the ASP.NET trace context, console windows, event logs, disk files,
the debugger, a remoting sink, e-mail, UDP datagrams, and more.
- Multiple targets for a single log entry.
- Level-based logging to pare events down.
- As many different loggers as you like, arranged in a hierarchy for
- Dynamic runtime configuration via XML files.
- Static configuration via assembly attributes.
- Filtering on a per-target basis, so a target can decide which events
it wants to log.
- Flexible layout and rendering classes.
- A plug-in architecture for easy extension.
If log4net won't do the logging job for you, you're in trouble.
A Final Cautionary Note
So, a bug rears its ugly head in the field, and the customer sends
back a log file. Before you do anything else, take a mental step backwards
and just think for a minute. Don't start with the details of log files
and diagnostic information. Instead, take at least a few minutes to think
about what happened (or what might have happened) before opening the logs.
Can you deduce the cause of the error simply by realizing what you've
done wrong, or at least narrow down the portion of the code where it's
likely to be? Does the error message tell you exactly what happened? Did
the user try something that you never considered during testing? It's
good to have logging available as a way to collect information when a
truly mysterious bug occurs. But don't forget to look at the forest before
you focus in on the individual trees.
Are you using logging innovatively in your applications? Or is your
code so great that logging is a waste of time? You can get hold of me
at [email protected]. I'll
use the most interesting comments in a future issue of Developer Central.