Sharp Coders White Papersharpcoders.co.uk/White Papers/Sharp Coders White Paper - Dot Net... ·...

13
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/

Transcript of Sharp Coders White Papersharpcoders.co.uk/White Papers/Sharp Coders White Paper - Dot Net... ·...

Page 1: Sharp Coders White Papersharpcoders.co.uk/White Papers/Sharp Coders White Paper - Dot Net... · Sharp Coders Sharp Coders White Paper - .Net Logging Considerations 5 Example 1 –

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/

Page 2: Sharp Coders White Papersharpcoders.co.uk/White Papers/Sharp Coders White Paper - Dot Net... · Sharp Coders Sharp Coders White Paper - .Net Logging Considerations 5 Example 1 –

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.

Page 3: Sharp Coders White Papersharpcoders.co.uk/White Papers/Sharp Coders White Paper - Dot Net... · Sharp Coders Sharp Coders White Paper - .Net Logging Considerations 5 Example 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.

Page 4: Sharp Coders White Papersharpcoders.co.uk/White Papers/Sharp Coders White Paper - Dot Net... · Sharp Coders Sharp Coders White Paper - .Net Logging Considerations 5 Example 1 –

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.

Page 5: Sharp Coders White Papersharpcoders.co.uk/White Papers/Sharp Coders White Paper - Dot Net... · Sharp Coders Sharp Coders White Paper - .Net Logging Considerations 5 Example 1 –

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();

Page 6: Sharp Coders White Papersharpcoders.co.uk/White Papers/Sharp Coders White Paper - Dot Net... · Sharp Coders Sharp Coders White Paper - .Net Logging Considerations 5 Example 1 –

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); }

Page 7: Sharp Coders White Papersharpcoders.co.uk/White Papers/Sharp Coders White Paper - Dot Net... · Sharp Coders Sharp Coders White Paper - .Net Logging Considerations 5 Example 1 –

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); } }

Page 8: Sharp Coders White Papersharpcoders.co.uk/White Papers/Sharp Coders White Paper - Dot Net... · Sharp Coders Sharp Coders White Paper - .Net Logging Considerations 5 Example 1 –

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;

Page 9: Sharp Coders White Papersharpcoders.co.uk/White Papers/Sharp Coders White Paper - Dot Net... · Sharp Coders Sharp Coders White Paper - .Net Logging Considerations 5 Example 1 –

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.

Page 10: Sharp Coders White Papersharpcoders.co.uk/White Papers/Sharp Coders White Paper - Dot Net... · Sharp Coders Sharp Coders White Paper - .Net Logging Considerations 5 Example 1 –

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.

Page 11: Sharp Coders White Papersharpcoders.co.uk/White Papers/Sharp Coders White Paper - Dot Net... · Sharp Coders Sharp Coders White Paper - .Net Logging Considerations 5 Example 1 –

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.

Page 12: Sharp Coders White Papersharpcoders.co.uk/White Papers/Sharp Coders White Paper - Dot Net... · Sharp Coders Sharp Coders White Paper - .Net Logging Considerations 5 Example 1 –

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>

Page 13: Sharp Coders White Papersharpcoders.co.uk/White Papers/Sharp Coders White Paper - Dot Net... · Sharp Coders Sharp Coders White Paper - .Net Logging Considerations 5 Example 1 –

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