Sharp Coders Sharp Coders White Paper - .Net Logging Considerations
1
Sharp Coders White Paper
.NET Logging Considerations
Summary
Abstract: Microsoft’s .NET framework contains powerful logging features. Learning how to use them
aids both software development and post deployment software support and can help reduce the cost
of development and the cost of fixing issues after the software has been deployed.
© Copyright Sharp Coders Limited 2014
The copyright in this work is vested in Sharp Coders and is issued in confidence for the purpose for which it is
supplied. It must not be reproduced in whole or in part or used for tendering or purposes except under an
agreement or with the consent in writing of Sharp Coders and then only on the condition that this notice is
included in any such reproduction. No information as to the contents or the subject matter of this document
or any part thereof arising directly or indirectly there from shall be given orally or in writing or communicated
in any manner whatsoever to any third party being an individual firm or employee thereof without the prior
consent in writing of Sharp Coders.
Sharp Coders Limited
http://www.sharpcoders.co.uk/
Sharp Coders Sharp Coders White Paper - .Net Logging Considerations
2
1. .NET Logging Considerations
Introduction
This application note describes the role logging can play during development and during application
deployment. It then examines various features available within the .NET framework for logging and
works towards a practical real world example.
Intended Audience
This application note is primarily aimed at software engineers who are writing software using one of the .NET languages.
What is logging
Logging at its simplest level is a way of recording data overtime. The frequency and type of data is
determined by the developer and this data can then be analysed either as the data is recorded or at
a later date. A more practical example is a software program that logs any error it encounters into a
file. This file can then be examined and the data analysed to better understand why those particular
sets of errors are occurring.
Why use Logging
There is no hard and fast rule that you must use logging and for small trivial software applications it might not be worth the effort but logging is never worthless.
When software is in development it can be invaluable to be able to see information showing how the software is executed and where errors occur. This can lead to defects being spotted earlier and fixed quicker. When software is deployed at a customer site and they report an issue being able to analyse a log showing what the software was doing prior to the issue can be an invaluable tool in understanding the issue and finding an expeditious solution.
.NET Logging
A number of different logging solutions exist within the .NET world and a complete description can
be found at http://www.dotnetlogging.com/. This article is going to focus on the built in logging
features within the .NET framework.
Microsoft has created the concept of a source and a listener for logging purposes. A source in its
simplest form is an object that can be used to log some data. A listener captures the message from
the source and does something with it. It should be noted that you can have as many sources in your
application and as many listeners as you want. See Figure 1.
Sharp Coders Sharp Coders White Paper - .Net Logging Considerations
3
Figure 1 - High Level Overview of .NET Logging Objects
A more illustrative example would be a software application that is split into a graphical user
interface (GUI) and a business logic DLL. Logging consists of two trace source objects, one for the GUI
and one for the business logic DLL. Two listeners will be used to capture all the messages from the
two trace source objects, one will write the log messages to a file and one will display them in the
GUI. The high level view of how this would look is show in Figure 2.
Figure 2 - A Practical Example of Microsoft .NET Logging Objects
How to Use Microsoft .NET Logging
The rest of this article walks through a number of simple examples (with full source code links
provided at the end of this document) that show how to create useful logging for your application.
Sharp Coders Sharp Coders White Paper - .Net Logging Considerations
4
Logging Levels
Each log message is sent out at a log level. This allows the user to classify log messages on their importance. For example a message could be logged at high level every time an exception is thrown. While a lower level message could be logged whenever a method is called. This allows the listener to set a log level which is should display. So for example when an application is deployed it might be better to just log warnings and errors to reduce the amount logged so that important issues are seen. .NET logging supports the following logging level: Critical, Error, Warning, Information, Verbose, Start, Stop, Suspend, Resume, and Transfer. E.g. with the logging level set to warning only Critical, Error and Warning messages will be logged.
Sharp Coders Sharp Coders White Paper - .Net Logging Considerations
5
Example 1 – A simple example with one source and two default listeners
In this example a simple console application is created. It creates a single trace source object and
two listener objects, one for logging to the console window and one to write to a file.
// Create a fileName string with the current date and time stamp. DateTime currTime = DateTime.SpecifyKind(DateTime.Now, DateTimeKind.Utc); string mStartTime = currTime.ToUniversalTime().ToString("yyyy-MM-dd_HH-mm-ss-mm"); string filename = "Dot Net Logging Example_" + mStartTime + ".txt"; // Create the TraceSource object. This is the 'source' of logging. myTraceObj = new TraceSource("MyExampleLogging"); // Now Add some listeners. One is a file and one is stdOut TextWriterTraceListener twtl = new TextWriterTraceListener(File.Create(filename)); ConsoleTraceListener ctl = new ConsoleTraceListener(); // Set the listener output to append the thread ID and the dateTime. twtl.TraceOutputOptions = TraceOptions.DateTime | TraceOptions.ThreadId; ctl.TraceOutputOptions = TraceOptions.DateTime | TraceOptions.ThreadId; myTraceObj.Listeners.Add(twtl); myTraceObj.Listeners.Add(ctl); // Flushes all the trace listeners in the trace listener collection after each write. Trace.AutoFlush = true; // Now create a SourceSwitch which is the way of controlling logging levels. SourceSwitch mytraceswitch = new SourceSwitch("name"); // Set the log level to Information. Now all Information, warning and error messages will be disaplyed. mytraceswitch.Level = SourceLevels.Information; // Now set the switch property in our trace source object to define the logging level. myTraceObj.Switch = mytraceswitch; // Log some error messages. myTraceObj.TraceEvent(TraceEventType.Error, 0, "An Error log message!!"); myTraceObj.TraceEvent(TraceEventType.Warning, 0, "A Warning log message!!"); myTraceObj.TraceEvent(TraceEventType.Information, 1, "An Information log message!!"); myTraceObj.TraceInformation("An Information log message!!"); // The following line won’t get displayed as the log level is set to information. myTraceObj.TraceEvent(TraceEventType.Verbose, 1, "A verbose log message.!!"); // Change the log level to verbose. Now all verbose, information, warning and error messages will be displayed. myTraceObj.Switch.Level = SourceLevels.Verbose; myTraceObj.TraceEvent(TraceEventType.Error, 0, "An Error log message!!"); myTraceObj.TraceEvent(TraceEventType.Warning, 0, "A Warning log message!!"); myTraceObj.TraceEvent(TraceEventType.Information, 1, "An Information log message!!"); myTraceObj.TraceInformation("An Information log message!!"); // The following line will get displayed as the log level is set to verbose. myTraceObj.TraceEvent(TraceEventType.Verbose, 1, "A verbose log message.!!"); // Flushes the output buffer for the TraceListener. myTraceObj.Flush(); // Closes the output to the stream specified for this trace listener. myTraceObj.Close();
Sharp Coders Sharp Coders White Paper - .Net Logging Considerations
6
Example 2 – How to create a custom trace source object.
One of the problems with example 1 is that the default trace source object requires quite a complex
line of code to raise a log message:
myTraceObj.TraceEvent(TraceEventType.Warning, 0, "A Warning log message!!");
Example 2 shows how to create a custom trace source object that encapsulates the complex line of
code into a more user friendly method:
myCustomTraceObj.TraceWarning("A Warning log message!!");
By inheriting from the TraceSource class and then adding a number of methods that call the
TraceEvent method in the base class the custom trace source simplifies the log message method and
can easily be extended to create custom log methods. Each TraceEvent method takes an ID number
that can be used to identify the message, for the custom trace source object it is being used to
distinguish instances of the custom trace source object.
class CustomTraceSource : TraceSource { /// <summary> /// Static counter to keep track of how many custom trace source objects /// have been created. /// </summary> private static int classCounter = 0; /// <summary> /// Member variable to store this objects class counter value. /// </summary> private int currentClassCounter; /// <summary> /// Constructor for the TraceSources class. /// </summary> /// <param name="name">Name to identify the trace source</param> public CustomTraceSource(string name) : base(name, SourceLevels.Information) { currentClassCounter = classCounter; classCounter++; } /// <summary> /// Log Error messages. /// </summary> /// <param name="format">Message to be displayed.</param> public void TraceError(string format) { base.TraceEvent(TraceEventType.Error, currentClassCounter, format); } /// <summary> /// Log Error messages with arguments. /// </summary> /// <param name="format">Message to be displayed.</param> /// <param name="args">Argument to display.</param> public void TraceError(string format, params object[] args) { base.TraceEvent(TraceEventType.Error, currentClassCounter, format, args); }
Sharp Coders Sharp Coders White Paper - .Net Logging Considerations
7
/// <summary> /// Log verbose messages. /// </summary> /// <param name="format">Message to be displayed.</param> public void TraceVerbose(string format) { base.TraceEvent(TraceEventType.Verbose, currentClassCounter, format); } /// <summary> /// Log Verbose messages with arguments. /// </summary> /// <param name="format">Message to be displayed.</param> /// <param name="args">Argument to display.</param> public void TraceVerbose(string format, params object[] args) { base.TraceEvent(TraceEventType.Verbose, currentClassCounter, format, args); } /// <summary> /// Log warning messages. /// </summary> /// <param name="format">Message to be displayed.</param> public void TraceWarning(string format) { base.TraceEvent(TraceEventType.Warning, currentClassCounter, format); } /// <summary> /// Log Warning messages with arguments. /// </summary> /// <param name="format">Message to be displayed.</param> /// <param name="args">Argument to display.</param> public void TraceWarning(string format, params object[] args) { base.TraceEvent(TraceEventType.Warning, currentClassCounter, format, args); } /// <summary> /// Log information messages. /// </summary> /// <param name="format">Message to be displayed.</param> public new void TraceInformation(string format) { base.TraceEvent(TraceEventType.Information, currentClassCounter, format); } /// <summary> /// Log information messages with arguments. /// </summary> /// <param name="format">Message to be displayed.</param> /// <param name="args">Argument to display.</param> public new void TraceInformation(string format, params object[] args) { base.TraceEvent(TraceEventType.Information, currentClassCounter, format, args); } }
Sharp Coders Sharp Coders White Paper - .Net Logging Considerations
8
Example 3 – How to create a custom listener object.
Example 3 shows how to create a custom listener so we can control the output from our listener.
The CustomLister inherits from TraceListener and overrides the Write and WriteLine methods
changing the colour or each message depending on the log level.
public class CustomListener : TraceListener { // Variable to hold the partial message. When a message is sent to a listener it // is split up as a write and a writeline. The Write contains the source name and // log level while the writeline contains the message to log. private string partialMessage = string.Empty; /// <summary> /// Method that writes colour coded for log level message to the console. /// </summary> /// <param name="Message">Message to write.</param> public override void WriteLine(string Message) { string FullMessage = partialMessage + Message; if (FullMessage.Contains("Information")) { Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(FullMessage); Console.ForegroundColor = ConsoleColor.Gray; } else if (FullMessage.Contains("Warning")) { Console.ForegroundColor = ConsoleColor.Yellow; Console.WriteLine(FullMessage); Console.ForegroundColor = ConsoleColor.Gray; } else if (FullMessage.Contains("Error")) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(FullMessage); Console.ForegroundColor = ConsoleColor.Gray; } } /// <summary> /// Method that writes a string to a listBox control on the UI thread with no CR. /// </summary> /// <param name="Message">Message to write.</param> public override void Write(string Message) { partialMessage = Message; } }
A small change to the “Program.cs” file to now instantiate the custom listener and add it to the list
of listener objects.
// Now Add some listeners. One is a a file amd one is stdOut TextWriterTraceListener twtl = new TextWriterTraceListener(File.Create(filename)); CustomListener ctl = new CustomListener(); // Set the listener output to append the thread ID and the dateTime. twtl.TraceOutputOptions = TraceOptions.DateTime | TraceOptions.ThreadId; ctl.TraceOutputOptions = TraceOptions.DateTime | TraceOptions.ThreadId;
Sharp Coders Sharp Coders White Paper - .Net Logging Considerations
9
myCustomTraceObj.Listeners.Add(twtl); myCustomTraceObj.Listeners.Add(ctl);
The result of using this custom listener is that we now have a colour coded output as can be seen in
Figure 3.
Figure 3 - Colour Coded log messages using the CustomListener object.
Sharp Coders Sharp Coders White Paper - .Net Logging Considerations
10
Example 4 – Working with multiple Instances of the TraceSource class.
Example 4 shows a slightly more complex logging example where a separate trace source has been
used for 3 different classes. Class A is then instantiated twice. The example shows how to use .NET
logging to easily distinguish between instances of the same class. This can be achieved by adding a
static counter to the custom TraceSource and incrementing it every time the constructor is called:
/// <summary> /// Static counter to keep track of how many custom trace source objects /// have been created. /// </summary> private static int classCounter = 0; /// <summary> /// Member variable to store this objects class counter value. /// </summary> private int currentClassCounter; /// <summary> /// Constructor for the TraceSources class. /// </summary> /// <param name="name"></param> public CustomTraceSource(string name) : base(name, SourceLevels.Information) { currentClassCounter = classCounter; classCounter++; }
Then modifying each TraceSource method to incorporate the counter value at the time the class was
instantiated allows the log messages from instantiations of the same class to be differentiated:
/// <summary> /// Log Error messages. /// </summary> /// <param name="format">Message to be displayed.</param> public void TraceError(string format) { base.TraceEvent(TraceEventType.Error, currentClassCounter, format); }
The result of using multiple instances of the custom trace source class can be seen in Figure 4.
Sharp Coders Sharp Coders White Paper - .Net Logging Considerations
11
Figure 4 - Multiple Instances of the CustomTraceSource Class
Note: Deciding on how many trace source object s to have is at the discretion of the developer.
Sharp Coders Sharp Coders White Paper - .Net Logging Considerations
12
Example 5 – Using a Configuration files to setup and control logging.
One benefit of logging as discussed earlier is to specify different levels of logging and then have the
listener only record certain levels of logging. If the level is set in the code then the ability to change
the log level can only be made by recompiling the software. The built in .NET logging allows you to
specify both the listeners, the log level and what source(s) map to what listeners in a configuration
file which can be changed without recompiling the software. By adding an “App.config” file to the
solution as seen in Figure 5. The creation of the listeners and the log level setting can be controlled.
Figure 5 - Adding an Application Configuration File
More details on the App.Config file can be found at http://msdn.microsoft.com/en-
us/library/aa374182(v=VS.85).aspx . The App.Config used in example 4 defines 3 sources and 2
custom listeners.
<?xml version="1.0" encoding="utf-8" ?> <configuration> <system.diagnostics> <trace autoflush="true"/> <sources> <source name="ClassA" switchName="classALogLevel"> <listeners> <remove name="Default"/> <add name="consoleListener"/> <add name="myListener"/> </listeners> </source>
Sharp Coders Sharp Coders White Paper - .Net Logging Considerations
13
<source name="ClassB" switchName="classBLogLevel"> <listeners> <remove name="Default"/> <add name="consoleListener"/> <add name="myListener"/> </listeners> </source> <source name="ClassC" switchName="classCLogLevel"> <listeners> <remove name="Default"/> <add name="consoleListener"/> <add name="myListener"/> </listeners> </source> </sources> <switches> <add name="classALogLevel" value="Information"/> <add name="classBLogLevel" value="Information"/> <add name="classCLogLevel" value="Information"/> </switches> <sharedListeners> <add name="consoleListener" type="SharpCoders.Examples.Dot_Net_Logging_Example_05.CustomListener,Dot Net Logging Example 05"/> <add name="myListener" type="System.Diagnostics.TextWriterTraceListener" initializeData="myListener.log" /> </sharedListeners> </system.diagnostics> </configuration>
This removes all the code that created our custom listeners and the passing of the listeners to each
class we wanted to have a trace source in.
Conclusion
Here at Sharp Coder’s we believe that logging is a fundamental part of application development and
hopefully through this article and the companion software examples both the benefits of logging and
a practical understanding of how to use Microsoft’s .NET logging features has been given.
Source code for all the examples contained in this Sharp Coders White Paper can be found here:
www.sharpcoders.co.uk/downloads/Dot Net Logging Example.zip