February 28, 2021
Hot Topics:

Building a Logging Object in .NET

  • By Mark Strawmyer
  • Send Email »
  • More Articles »

From the .NET Nuts & Bolts column on CodeGuru.com.

Welcome to the next installment of the .NET Nuts & Bolts column. In this column we talk about how you can use some of the classes in the .NET Framework to log information to different locations. The locations of choice in this article will be to a file and the windows event log.

Even though many programmers deny it about their code, or try using some name such as a "feature" to make it sound better, code will sometimes contain defects and errors. Whether that error is caused by a flawed design, bad specifications, incorrect formula, or the fact that you worked on it so long without sleep you were seeing things, you will eventually need a way to capture error messages to some location. In addition to error messages, it may also be advantageous to capture information about your code for performance or some other reason.

This brings me to the focus of this column. We'll explore how to build an object that can log information to a file or the event log. A database is another logical location to log information, but since I've covered some database stuff in the past, for this article I'll focus on accessing files and the event log.

Designing the Logging Object

Step one is to identify the problem. We want to build some type of logger object that can log to multiple locations. There are plenty of ways to approach this. One way would be to create different classes for each type of log we want to use then just create and use the desired class in our code. The downside to this approach is that it locks us into logging to the same place all of the time, or we have to put a bunch of duplicated logic in our code to decide which object to create and use. A better way to handle this, at least in my mind, is to have a single logger object with which to interact. This logger can be configurable to which location it will record information. Based on how it is configured at run time, it will create and log messages to the appropriate location.

Now we've decided on a single object to interact with when logging, and we are going to allow the object to log to different locations. Wouldn't it be nice to have components that can be added to the logger or taken out rather than have to add a bunch of code to or take away code from the logger? In order to accomplish this, we'll create an abstract class that defines the methods our individual log objects should contain. If all of the individual logging classes our logger uses all implement the same interface it makes our logger relatively simple.

The last remaining item is to define what functionality our logger and its individual log components should have. To come up with our definition we need to think about what methods and properties we want our logger to provide. It stands to reason since the primary purpose of our logger is for error logging we should have a method that accepts an exception as an input parameter. It also stands to reason that if we plan to use our logger to record informational errors as well that we'll need another method that accepts a generic message along with an indicator of whether the message is an error or some other type of informational or warning message that will control how the logging occurs.

First we'll define a base class for our log objects to ensure that our logger object can interact with each of them. After the base class is defined, we'll create the individual classes that will handle logging to a file and the event log, and then we'll tie it all together by creating the logger component.

Sample Abstract Log Class

The following code outlines a base class for the log objects with which our logger will interact.

using System;namespace CodeGuru.ErrorLog.Logs{  /// <remarks>  /// Abstract class to dictate the format for the logs that our   /// logger will use.  /// </remarks>  public abstract class Log  {   /// <value>Available message severities</value>   public enum MessageType   {     /// <value>Informational message</value>     Informational = 1,     /// <value>Failure audit message</value>     Failure = 2,     /// <value>Warning message</value>     Warning = 3,     /// <value>Error message</value>     Error = 4   }     public abstract void RecordMessage(Exception Message,                                    MessageType Severity);     public abstract void RecordMessage(string Message,                                   MessageType Severity);  }}

Creating the Logging Object to Write to a File

Reading and writing to files is accomplished through classes in the System.IO namespace. The FileStream object is used to read or write files. The StreamReader and StreamWriter are used in conjunction with the FileStream to perform the actual action. Below we'll create an object that extends our Log base class and that uses the FileStream and the StreamWriter to write a message to a file.

Sample File Logging Class

using System;using System.IO;using System.Text;namespace CodeGuru.ErrorLog.Logs{  /// <remarks>  /// Log messages to a file location.  /// </remarks>  public class FileLog : Log  {   // Internal log file name value   private string _FileName = "";   /// <value>Get or set the log file name</value>   public string FileName   {     get { return this._FileName; }     set { this._FileName = value; }   }   // Internal log file location value   private string _FileLocation = "";   /// <value>Get or set the log file directory location</value>   public string FileLocation   {     get { return this._FileLocation; }     set      {        this._FileLocation = value;        // Verify a '\' exists on the end of the location       if( this._FileLocation.LastIndexOf("\\") !=                  (this._FileLocation.Length - 1) )       {          this._FileLocation += "\\";       }     }   }   /// <summary>   /// Constructor   /// </summary>   public FileLog()   {      this.FileLocation = "C:\\";      this.FileName = "mylog.txt";   }   /// <summary>   /// Log an exception.   /// </summary>   /// <param name="Message">Exception to log. </param>   /// <param name="Severity">Error severity level. </param>   public override void RecordMessage(Exception Message,             Log.MessageType Severity)   {     this.RecordMessage(Message.Message, Severity);   }   /// <summary>   /// Log a message.   /// </summary>   /// <param name="Message">Message to log. </param>   /// <param name="Severity">Error severity level. </param>   public override void RecordMessage(string Message,             Log.MessageType Severity)   {     FileStream fileStream = null;     StreamWriter writer = null;     StringBuilder message = new StringBuilder();     try     {        fileStream = new FileStream(this._FileLocation +                  this._FileName, FileMode.OpenOrCreate,                  FileAccess.Write);      writer = new StreamWriter(fileStream);        // Set the file pointer to the end of the file      writer.BaseStream.Seek(0, SeekOrigin.End);        // Create the message      message.Append(System.DateTime.Now.ToString())         .Append(",").Append(Message);      // Force the write to the underlying file      writer.WriteLine(message.ToString());      writer.Flush();     }     finally     {      if( writer != null ) writer.Close();     }   }  }}

Page 1 of 2

This article was originally published on February 19, 2003

Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Thanks for your registration, follow us on our social networks to keep up-to-date