Thursday, April 3, 2008

Do your .NET assemblies have Trace Statements? Maybe they should.

Have you ever had a problem with your code in a production environment? If not you must be awful lucky. For the rest of us I'm sure we have spent may hours troubleshooting code that worked fine in development, but is now not working in the production environment. If you've used the Debug class to output debug messages you mostly like have found that these don't work in release builds. Yes you can enable them in the release build, but a better way to handle this is to use the Trace class.

You can write a Trace message very easily.


Trace.WriteLine("I'm doing some work");


This will write "I'm doing some work" to the output window. If I'm in a production environment I might want to write to a text file instead. Trace handles this by implementing TraceListeners. The default is the DefaultTraceListener which writes to the output window. We can use the TextWriterTraceListener to output to a file. We can do this via a configuration file or programmatically. If you want to add this to the configuration file add the following to your app.config file.


<configuration>
<system.diagnostics>
<trace autoflush="false" indentsize="2">
<listeners>
<remove name="Default" />
<add name="TextTraceLogger" type="System.Diagnostics.TextWriterTraceListener" initializeData="Log.Txt" />
</listeners>
</trace>
</system.diagnostics>
</configuration>


In this configuration we removed the Default Listener. This isn't required. You can have many listeners if you like. If you are adding Trace messages to libraries be sure to add the Trace configuration to your executing assembly. You can add a Listener programmatically by using the Trace.Listeners property.


Trace.Listeners.Add(new TextWriterTraceListener("Log.Txt"));


Especially in production you don't want detailed output all the time. You just want it when you are troubleshooting. The TraceSwitch class performs this function. The TraceSwitch class stores a TraceLevel that is used to evaluate whether a message should be outputted or not.

public enum TraceLevel
{
Off = 0,
Error = 1,
Warning = 2,
Info = 3,
Verbose = 4,
}


When a TraceSwitch is set to a specific TraceLevel all the lower valued levels are "activated" as well. For example if a TraceLevel is set to Warning, Error messages will be outputted as well.

Like the TraceListeners the TraceSwitch can be created in the configuration file or programmatically. Configuration example:

<configuration>
<system.diagnostics>
<switches>
<add name="General" value="Warning" />
</switches>
</system.diagnostics>
</configuration>


If you are adding TraceSwitches to a library be sure to set the configuration of the executing assembly and not the libraries assembly. Programmatic example:

TraceSwitch GeneralTrace = new TraceSwitch("General", "General Trace Messages");
GeneralTrace.Level = TraceLevel.Warning;


To use a TraceSwitch in code you first need to declare it. If you have defined a TraceSwitch in the configuration file the TraceSwitch name needs to match the name in the configuration file. You then will use the TraceSwitch to evaluate weather a message is outputted or not.

TraceSwitch GeneralTrace = new TraceSwitch("General", "General Trace Messages");

Trace.WriteLineIf(GeneralTrace.TraceVerbose, "A verbose message");
Trace.WriteLineIf(GeneralTrace.TraceError, "an error message");


If you need to do some work to create the Trace message you may want to just include the TraceSwitch evaluation in an if block.

if (GeneralTrace.TraceError)
{
StringBuilder errorMessageBuilder = new StringBuilder();
errorMessageBuilder.Append("Error processing. Details: ");
foreach (ErrorDetail detail in ErrorDetails)
{
errorMessageBuilder.Append(detail.Message);
}

Trace.Write(errorMessageBuilder.ToString());
}


Here are some other TraceListeners:

You can also create your own TraceListener by inheriting from TraceListener.

Save to del.icio.us Add to Technorati Add to dzone

No comments: