Click here to Skip to main content
Click here to Skip to main content

A C# Central Logging Mechanism using the Observer and Singleton Patterns

By , 18 Feb 2008
 

Introduction

Logging is something any respectable application needs to do. Whether it's to keep track of what the application is doing under the hood (for possible trouble shooting), to show a list of events that we might need to inform the user of, or simply to keep a history of events, logging is as necessary to the programming world as note taking is to a class room. If you don't do it, you might be able to get by for the moment. Just hope you never need to refer back to something that happened a few minutes, hours or days ago.

This article shows an excellent mechanism for adding logging to a C# project. It is extremely powerful and versatile, yet very simple to implement and use. It uses two very common programming patterns: the singleton and the observer.

Background

The ideas and code shown in here are not rocket science, so as long as you have at least a basic idea of object oriented programming, and have been playing around with C# for a while, I'd say you're safe.

This having been said, it is always a good idea to brush up on a couple of things if you want to get the most out of this. I would suggest reading up on patterns, and focusing on some of the more common ones such as the Singleton and Observer.

To keep it brief and concise, however, I'll delve into these two for a bit and give you an idea of what they're about.

The Singleton Pattern

In Layman's Terms ...

Probably the most widely known and one of simplest patterns, the singleton is a mechanism that ensures all objects in your process have access to a shared and single set of data and methods.

Imagine an intersection where several roads meet, and where the cars need to ensure they do not crash into each other when crossing this intersection. Assuming there were no traffic lights to guide them, we would need a police officer standing in the middle to direct traffic. This officer would be a real life approximation of a singleton.

The officer would receive visual queues from all roads, telling him which one is filling up the most with cars. With all this information, he would be able to decide which road to let the cars come through, and which roads to block.

This would be significantly more complex if there were several officers controlling the intersection, where they would all be receiving visual queues from the roads, would have to communicate with each other and then collectively decide which road to let traffic come from.

Similarly, a singleton object is something shared by all the objects in the system. For the purposes of the logging mechanism, the singleton is the one and only object to which all other objects will send the information they wish to have logged.

Not only does this centralize and simplify control of the logging mechanism, this also gives the developer an excellent way to provide uniform formatting to the log output. You can add timestamps, titles, parameter information and more in just one place. None of the objects that need information to be logged need be concerned with this, as the singleton logger takes care of it on its own.

Putting This In Code ...

When regular classes need to be used, they are instantiated as an object and then used. A singleton class, however, does not allow anyone except itself to instantiate it. This guarantees that only one copy of this object will ever run, and all objects in the program will be accessing this one copy.

To accomplish this, the logger has only one private constructor and a private variable of its own type. The only way to really get a hold of this object from the outside then is to instantiate the Logger class and equate the new object to the static handle exposed in the class. The Instance property then checks to see if the private mLogger object was ever created, and if it was not, this is the only place where Logger will ever get instantiated.

class Logger
{
    private static object mLock;
    private static Logger mLogger = null;
    // the public Instance property everyone uses to access the Logger
    public static Logger Instance
    {
        get 
        { 
            // If this is the first time we're referring to the
            // singleton object, the private variable will be null.
            if (mLogger == null)
            {
                // for thread safety, lock an object when
                // instantiating the new Logger object. This prevents
                // other threads from performing the same block at the
                // same time.
                lock(mLock)
                {
                    // Two or more threads might have found a null
                    // mLogger and are therefore trying to create a 
                    // new one. One thread will get to lock first, and
                    // the other one will wait until mLock is released.
                    // Once the second thread can get through, mLogger
                    // will have already been instantiated by the first
                    // thread so test the variable again. 
                    if (mLogger == null)
                    {
                        mLogger = new Logger();
                    }
                }
            }
            return mLogger; 
        }
    }
    // the constructor. usually public, this time it is private to ensure 
    // no one except this class can use it.
    private Logger()
    { 
        mLock = new object();
    }
}
public class SomeWorkerClass
{
    // any class wanting to use the Logger just has to create a 
    // Logger object by pointing it to the one and only Logger
    // instance.
    private Logger mLogger = Logger.Instance;
}

The Observer Pattern

In Layman's Terms Again ...

This is where things get a bit more complex, but a bit more fun as well. The observer pattern is simply a mechanism where one object (such as our police officer from the example above) has the ability to dispatch a message to one or more observers. What they do with this message is completely up to them, and in fact, the officer doesn't even know who they are or what they do. He just knows how many observers there are, and has a predefined method for giving them this information.

Going back to the traffic example: whenever the officer needs to let traffic on one road stop moving, he shows his open palm to that road's direction. He is in effect sending all traffic, which is observing him for input, a message. The traffic facing him will interpret this message and stop, and all other traffic will interpret the message and subsequently ignore it has no effect on them.

Next, he waives to traffic in another road indicating that the drivers should start moving. Again, he has sent another message, and all the drivers will receive it in the same way, but act on it in different ways. Those facing him will start moving, and all others will simply ignore the message.

So far, we have one observed subject (the officer) and several observers (the drivers). The beauty of this pattern, however, is that there could be observers of other types as well, and as long as they can receive the officer's messages in the same way as the drivers (i.e. as long as they have eye sight and are looking at him), they can act on the messages as well. A simple example would be for the officer to actually be a cadet on training, and a senior officer to be sitting in his car watching this cadet and taking notes of the stop and go messages. Again, the senior officer is observing the cadet and receiving his messages in the same way as the drivers, but is acting on them in a different manner.

In programming terms, the officer would be sending his messages to the observers via an interface. If a given object implements an interface, any other object can interact with it through the properties and methods exposed in that interface without even knowing what the true nature of the object really is.

Putting This In Code As Well ...

The first part of this pattern is the use of an interface which will allow the Logger to dispatch new log entries to the objects observing it. The beauty of this is that those objects could be of literally any type and offer all sorts of functionality. They could be forms, file writers, database writers, etc. But the Logger will only know that they implement this interface and will be able to communicate with them through it.

interface ILogger
{
    void ProcessLogMessage(string logMessage);
}

Next is the management of these observers within Logger. We need an array (or List<>) to store them (technically speaking, though, we're only storing a reference to them), and a public method by which they can be added to the array:

class Logger
{
    private List<ILogger> mObservers;
    private Logger()
    { 
        mObservers = new List<ILogger>();
    }
    public void RegisterObserver(ILogger observer)
    {
        if (!mObservers.Contains(observer))
        {
            mObservers.Add (observer);
        }
    }
}

Whenever we want to implement a new observer, we just need to ensure it implements the ILogger interface and does something meaningful when the ProcessLogMessage method is executed. An example would be a FileLogger object that writes the log messages to a file:

class FileLogger: ILogger
{
    private string mFileName;
    private StreamWriter mLogFile;
    public string FileName
    {
        get 
        { 
            return mFileName; 
        }
    }
    public FileLogger(string fileName)
    {
        mFileName = fileName;
    }
    public void Init()
    {
        mLogFile = new StreamWriter(mFileName);
    }
    public void Terminate()
    {
        mLogFile.Close();
    }
    public void ProcessLogMessage(string logMessage)
    {
    // FileLogger implements the ProcessLogMessage method by
    // writing the incoming message to a file.
        mLogFile.WriteLine(logMessage);
    }
}

This class would then be instantiated and passed to Logger as follows:

public partial class Form1 : Form
{
    // the main Logger object
    private Logger mLogger;
    // a logger observer that will write the log entries to a file
    private FileLogger mFileLogger;
    private void Form1_Load(object sender, EventArgs e)
    {
        // instantiate the logger
        mLogger = Logger.Instance;
        // instantiate the log observer that will write to disk
        mFileLogger = new FileLogger(@"c:\temp\log.txt" );
        mFileLogger.Init();
        // Register mFileLogger as a Logger observer.
        mLogger.RegisterObserver(mFileLogger);
    }
    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        // The application is shutting down, so ensure the file 
        // logger closes the file it's been writing to.
        mFileLogger.Terminate();
    }
}

Putting the Patterns Together

Our logger will use both of these patterns, and the attached sample code uses two observers: the FileLogger mentioned above, and the actual form itself. The FileLogger logs the messages to a file, and the form shows the messages in a textbox control. This is obviously a simple implementation, but it could be used in more complex scenarios. We could have an observer that writes the log entries to a database table, another that concatenates all the entries and then emails the log, etc.

Whenever the form needs to have something logged, it simply executes the AddLogMessage() on the logger and passes it the log entry:

class Logger
{
    public void AddLogMessage(string message)
    {
        // Apply some basic formatting like the current timestamp
        string formattedMessage = string.Format("{0} - {1}", 
		DateTime.Now.ToString(), message);
        foreach (ILogger observer in mObservers)
        {
            observer.ProcessLogMessage(formattedMessage);
        }
    }
}
public partial class Form1 : Form
{
    private void button1_Click(object sender, EventArgs e)
    {
        mLogger.AddLogMessage("The button was clicked.");
    }
}

Using the Code

The sample project does not perform any complex operations, but showcases the logging discussed in this article. The main form has a button that, when clicked, increments a private counter and displays the value in a text box.

Every time the counter is increased, however, the logger is informed. In turn, the logger formats the message it receives and dispatches it to all the observers for processing.

As a nice plus, the Logger's AddLogMessage() method is overwritten to accept an exception. If the application throws an exception and we want it properly logged, we just pass the exception to the Logger and it extracts all the messages from the exception and inner exceptions (if applicable), puts them together, adds the stack trace, and then logs the whole thing. Very useful.

In Conclusion

I hope this article and the attached sample prove useful. If you have any suggestions for improvement I'm all ears, so post a message and let us know what you think!

History

  • 17th November, 2007 – Initial post

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

David Catriel
Team Leader
Canada Canada
Member
A developer that's been tinkering with computers since he first laid eyes on his buddy's Atari in the mid 80's and messed around with GWBasic and Logo. He now divides his time among his wife, kids, and evil mistress (a term lovingly [ahem...] given to his computer by the wife ...).

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralVS2008. Bug: Missing foldermembertlhintoq25 Feb '08 - 14:20 
After downloading and opening in VS2008pro (conversion wizard no errors) the first run produced this error:
 
An unhandled exception of type 'System.IO.DirectoryNotFoundException' occurred in mscorlib.dll
 
Additional information: Could not find a part of the path 'c:\temp\log.txt'.
 
Manually making a c:\temp\ directory cures this. But the application really should do it.
 
My solution was:
public void Init()
{
// Presuming a path of "c:\temp\log.txt" you first need to
// make sure you have the directory to put the text file into.
Directory.CreateDirectory(Path.GetDirectoryName(mFileName));
// If folder already exists this does not erase/replace the
// existing directory.
 
mLogFile = new StreamWriter(mFileName);
}

GeneralRe: VS2008. Bug: Missing foldermemberMuaddubby25 Feb '08 - 14:44 
That's a good point, and it didn't occur to me. This having been said, however, the goal was to focus on showing a possible usage for one of the observers without getting into too much detail. If such an implementation were to be made in a real application, the developer would obviously have to ensure these paths exist.
 
I'll update the code sample to do something like this.
 
Thx.
 

GeneralError in the implementationmemberMember 395379718 Feb '08 - 1:18 
I'm afraid the class Logger (as it is shown on 2008/02/18) does not compile. Obviously you wanted to have mLock field static too and assign it once only, i.e.

private static object mLock = new object();
private Logger() { }

GeneralRe: Error in the implementationmemberMuaddubby18 Feb '08 - 2:09 
Hi
 
I just downloaded and tried compiling the project as it appears in the attachment, and had no problems. Can you tell me what the error was? Also, which version of VS are you using, and which framework are you referencing?
 

GeneralRe: Error in the implementationmemberMember 395379718 Feb '08 - 2:39 
I was not referring the code in the attachment. I was referring the code as it is shown in the text of the article ("The Singleton pattern"/"Putting this in code ...").
GeneralRe: Error in the implementationmemberMuaddubby18 Feb '08 - 6:22 
Thank you, I just saw it. The article has been updated.
 

GeneralVery nice article - one suggestion about creating singleton in C#memberNatza Mitzi10 Feb '08 - 8:36 
class Logger
{
      private static Logger mLogger;
 
      /// <summary>
      /// The static cotr is guaranteed to be accessed only once in the application
      /// before any member is created or a static member is called - simple and thread safe
      /// </summary>
      static Logger()
      {
            mLogger = new Logger();
      }
 
      public static Logger Instance
      {
            get
            {
                  return mLogger;
            }
      }
}
 
I also removed the null assignment since it is redundant.
 
Natza Mitzi
GeneralRe: Very nice article - one suggestion about creating singleton in C#memberMuaddubby10 Feb '08 - 9:01 
Hi Natza
 
Nice approach, but there's one problem: A singleton class shouldn't be able to instantiate itself via the new keyword, and by leaving the constructor static you now allow this. I tested the private members to ensure they really are all the same when instantiating the object several times via new, and they're ok.
 
This seems mostly like a question in semantics, but I would certainly avoid this because it is misleading. A singleton object is supposed to get its handle via an Instance property and not the new keyword. If you let the user use new, it could be confusing.
 

GeneralRe: Very nice article - one suggestion about creating singleton in C#memberNatza Mitzi10 Feb '08 - 22:25 
Hi Muad'Dubby,
 
The instantiation occurs once the property is called in an implicit way (static cotrs are never executed explicitly and executed only once in the applications life time). So if you think about it the static cotr behaves as a singleton like mechanism.
As you said it is just a different approach and I just thought it is worth mentioning.
 
Natza Mitzi
GeneralRe: Very nice article - one suggestion about creating singleton in C#memberMufaka15 Feb '08 - 19:59 
I'll throw another way out there:
 
public static readonly Logger Instance = new Logger();
 
private Logger() { }
GeneralRe: Very nice article - one suggestion about creating singleton in C#memberRay Hayes16 Feb '08 - 1:03 
This is my preferred method too.
 
Regards,
Ray

GeneralRe: Very nice article - one suggestion about creating singleton in C#memberMuaddubby16 Feb '08 - 2:48 
Interesting alternative, but I'm not sure about thread safety. The use of lock(mLock) ensures no two instantiations will occur simoultaneously, but I think your use of read-only might account for that.
 

GeneralRe: Very nice article - one suggestion about creating singleton in C#memberMufaka16 Feb '08 - 8:42 
I guess it just depends on how you are going to use it. In your example, you register observers before any threading issues would arise. The double-check locking that you do is the preferred way if you need to delay the instantiation in a multi-threaded environment, but in your case that is not needed.
 
MSDN has a writeup on different implementations. Implementing Singleton in C#[^]
GeneralRe: Very nice article - one suggestion about creating singleton in C#memberMuaddubby16 Feb '08 - 12:39 
Ok, cool. Thx for the MSDN link; should prove to be a good read.
 

GeneralNice articlememberDaniel Vaughan27 Dec '07 - 4:41 
This is great. I wish I found this earlier. I really like your use of the Observer pattern. It looks like we were thinking along the same lines with the centralized logging and my Clog project. Well done. 5 from me. Smile | :)
 



Daniel Vaughan


LinkedIn Profile
ShelfSpy

GeneralRe: Nice articlememberMuaddubby27 Dec '07 - 15:45 
The for the compliment. I've used this design in several projects and have found it to be quite robust, so I hope others find it handy too.
 
That having been said, I did run across an infuriating scenario today that made me realise there's a flaw in this. And it has to do with the singleton implementation.
 
Lets say your project is a user control, and you're using this logging mechanism in it. And lets say that in your host form (a second project), you end up putting two copies of this control. When it comes time to send some log entries to the Logger, or even for the Logger to dispatch the messages to the observers, you'll be dealing with only one singleton Logger for the entire host application, as opposed to one singleton Logger per user control.
 
I haven't thought out the consequences of this for the Logger, but I don't think they're serious. My problems today (in an unrelated project, but one that followed the basic premise above) caused a 15% hair loss (mostly by pulling), and gave me a very hard time.
 
Bottom line - try not to use singletons in user controls. If the control is implemented more than once in the same hosting form, you will have one singleton for the entire application, and not per user control.
 

GeneralDelegatesmemberlongdrver30 Nov '07 - 19:29 
would you recommend using delegates and events?
AnswerRe: DelegatesmemberMuaddubby1 Dec '07 - 3:13 
Logging isn't exactly brain surgery, so there are many good ways to implement it. I'm sure you could come up with a good mechanism that uses those two, but my preference would be to avoid using events.
 
The reason is that, in my opinion, it's much cleaner (and perhaps easier) to have the central logger call the observers through an interface than it is to raise and trap events. Visual Studio can automatically create all the interface methods for you in the observers (by right clicking on the interface and choosing to implement it). Granted, VS can also auto-implement your event trapping methods, but you have to find them one by one (by using intellisense on "Logger."), type the "+=", move on the next event (assuming you know what it is called), repeat the process ...
 
More importantly, I prefer the idea of the Logger knowing who he is broadcasting the log entries to. If you are using interfaces, then the Logger knows exactly who is listening to him, and this opens the door for more customization.
 
As for delegates - I've only had one coffee this morning, so am in no shape to sit down and think about how they would fit in. Just like events, though, I prefer staying away from them because I don't find them as straight-forward or as simple as interfaces.
 
Still, if you have a good idea on how to build a logger with these two, let everyone know. Better yet - write an article about it Smile | :)
 

GeneralAn Alternative you might want to look atmemberBrian Leach20 Nov '07 - 5:45 
An alternative you might want to look at is the log4net library available at http://logging.apache.org/log4net/  
Brian Leach

GeneralRe: An Alternative you might want to look atmemberMuaddubby20 Nov '07 - 7:06 
Yeah, I heard about log4net but have not looked into it yet. I'll do so soon.
 
Still, I sometimes prefer writing these things myself as they are infinitely (or almost so) more customizable than a ready-made solution from a 3rd party.
 
I guess it all comes down to a matter of taste Smile | :)
 

QuestionWhy not just use Trace?memberdimzon19 Nov '07 - 1:28 
You can write and attach your own Trace listeners
AnswerRe: Why not just use Trace?memberMuaddubby19 Nov '07 - 2:52 
Good point. Trace has similar functionality and does just about the same thing. It has a few advantages like having built-in support for indenting your entries and offering write/write line methods, but those could very easily be added to Logger.
 
The one thing I have not seen in Trace is the ability to offer centralised and user-defined formatting to your log entries. Remember that your entries could come from many places in your program, and you don't want to maintain formatting of that text in every one of those places. To avoid that, the formatting should be put in one place, such as Logger.AddLogMessage(). This way you make your log follow a consistent look and you don't have to worry about that throught your program.
 
This article, however, is also a good starting point for anyone trying to learn about the Observer and Singleton patterns, so if people only get that out of what I've written, I'm a happy man Smile | :)
 

 

 

GeneralRe: Why not just use Trace?memberdimzon19 Nov '07 - 2:58 
>The one thing I have not seen in Trace is the ability to offer centralised and user-defined
>formatting to your log entries. Remember that your entries could come from many places in your
>program, and you don't want to maintain formatting of that text in every one of those places.
>To avoid that, the formatting should be put in one place, such as Logger.AddLogMessage().
>This way you make your log follow a consistent look and you don't have to worry about that
>throught your program.
 
Actually You can implement Your own custom TraceListener and add Your formatting logic @ Write/WriteLine methods Wink | ;)
http://www.codeguru.com/vb/gen/vb_misc/debuggingandtracing/article.php/c5611/
GeneralRe: Why not just use Trace?memberMuaddubby19 Nov '07 - 3:08 
But what if I have several trace listeners (e.g. a form listener that shows the messages on screen, a file writer listener that writes to txt file, and a third listener that emails the log out) ? This would imply putting that formatting in every one of them. Not very efficient, and not the easiest thing to maintain.
 
If your intention is for all the logs to look different, then you could use this method and apply unique formatting in each of the listeners. In my experience, however, a requirement for logs has always been for them to have a consistent look no matter how they are received. Therefore, you'd have to centralize formatting somehow.
 

GeneralNicemembermikeperetz17 Nov '07 - 4:51 
Nice article. I believe logging is important, and there are many ways to do it.
 
I have wrote an article about composite pattern with logging, and it can be used as a hybrid with this model.
 
Logging with Composite pattern[^]
 
"There are only 10 types of people in the world: Those who understand binary, and those who don't"
More articles on my blog

GeneralRe: Nice [modified]memberMuaddubby17 Nov '07 - 11:28 
Thx. True, there are many ways to implement logging and they all have their own advantages. The observer/singleton method, I find, combines simplicity of implementation with robustness, so I'm quite happy with it.

 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web04 | 2.6.130516.1 | Last Updated 18 Feb 2008
Article Copyright 2007 by David Catriel
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid