Click here to Skip to main content
15,867,308 members
Articles / Web Development / ASP.NET
Article

Logging Using the Composite Pattern

Rate me:
Please Sign up or sign in to vote.
4.84/5 (36 votes)
15 Jul 2006CPOL3 min read 94K   746   95   20
Allow flexiable logging using the Composite design pattern.

Introduction

In this article, I will explain how I solved a common problem I used to have regarding logging within my business layer. Normally, I would have a function call from my presentation layer to my business layer; however, once in the business layer, I had no access to the presentation layer anymore. There are many ways to solve this problem. In the MFC days, a Doc / View approach based on the Observer pattern was used. In .NET, we can use custom events to notify the presentation layer that something happened. But, in this article, I will show the Composite pattern in action, and some of its nice advantages.

The Requirements

Suppose we had a function in our business layer that performs a lot of updates. It would be nice that during all these updates, we log the exact action taking place. We want to easily log a string describing the activity to one of, all of, or some of the following loggers: TextBox, ListBox, text file and/or the EventLog.

Creating the ILogger Interface

To keep the example simple, I will implement a simple ILogger interface that only requires a logger class to implement its own way of logging an activity.

C#
public interface ILogger
{
    void LogMessage(string strMessage);
}

Creating Logger Classes

Create the following logger classes:

  • Text box logger - logs a message into a text box
  • List box logger - adds a message to the list
  • File logger - logs a message into a file
  • EventLog logger - logs a message into the system event log

ListBox Logger

Notice that the ListBox and TextBox loggers are thread safe, by using the InvokeRequired method.

C#
class ListBoxLogger : ILogger
{
    ListBox m_listBox;
    public ListBoxLogger(ListBox listBox)
    {
        m_listBox = listBox;
    }

    public void LogMessage(string strMessage)
    {
        MethodInvoker logDelegate = delegate 
        {
            m_listBox.Items.Add(strMessage);
        };

        if (m_listBox.InvokeRequired)
            m_listBox.Invoke(logDelegate);
        else
            logDelegate();
    }
}

TextBox Logger

C#
class TextBoxLogger : ILogger
{
    private TextBox m_textBox;
    public TextBoxLogger(TextBox txtBox)
    {
        m_textBox = txtBox;
    }

    public void LogMessage(string strLogMessage)
    {
        MethodInvoker logDelegate = delegate { m_textBox.Text = strLogMessage; };
        if (m_textBox.InvokeRequired)
            m_textBox.Invoke(logDelegate);
        else
            logDelegate();
    }
}

File Logger

C#
class FileLogger : ILogger
{
    private string m_strFileName;
    private object m_sync = new object();
    public FileLogger(string strFileName)
    {
        m_strFileName = strFileName;
    }

    public void LogMessage(string strMessage)
    {
        lock (m_sync)
        {
            using (StreamWriter writer = new StreamWriter(m_strFileName))
            {
                writer.WriteLine(strMessage);
            }
        }
    }
}

Notice that the FileLogger is thread-safe, and that it opens and closes the file before writing to it. This guarantees that the message is flushed after each call to LogMessage.

EventLog Logger

C#
class EventLogger : ILogger
{
    public EventLogger()
    {

    }

    public void LogMessage(string strMessage)
    {
        EventLog.WriteEntry("Logger", strMessage);
    }
}

Using the ILogger Object

So far, there is nothing special here; we can have a function that takes a ILogger object and allow us to log messages. For example:

C#
private void DoSomthing(ILogger logger)
{
    for(int i=0; i < 10; i++)
    {
        logger.LogMessage("Logging a message " + i.ToString());
     
    }
}

The client code looks like this:

C#
// pass the File Logger
DoSomthing(new FileLogger("C://LogMessage.txt")); 
// txtBox is a text box control on the form
DoSomthing(new TextBoxLogger(textBox));

Introducing the CompositeLogger

But what if I want to log to all my loggers at the same time, or what if I want to log to only some of my loggers? To solve this requirement, I create a new logger called a Composite Logger.

C#
// Logger of composite of loggers
class CompositeLogger : ILogger
{
    private ILogger[] m_loggerArray;
    
    // pass a ILoggers that are part of this composite logger
    public CompositeLogger(params ILogger[] loggers)
    {
        m_loggerArray = loggers;
    }

    public void LogMessage(string strMessage)
    {
        // loop around all the loggers, and log the message.
        foreach (ILogger logger in m_loggerArray)
            logger.LogMessage(strMessage);
    }
}

Let's Use Our Composite Logger to "Configure" Which Loggers to Use

Using all the loggers:

C#
CompositeLogger compositeLogger =   
        new CompositeLogger(new TextBoxLogger(textBox),
                            new ListBoxLogger(listBox),
                            new FileLogger("C:\\LogPattern.txt"),
                            new EventLogger() );

Creating a composite logger with only TextBoxLogger and ListBoxLogger:

C#
CompositeLogger compositeLogger =   
        new CompositeLogger(new TextBoxLogger(textBox),
                            new ListBoxLogger(listBox));

Because our composite logger implements ILogger like all the other loggers, we can pass it to a function that expects an ILogger object type.

C#
DoSomthing(compositeLogger);

Testing the Composite Logger in a Thread

Let’s test our composite logger in a separate thread, just to make sure we can update the UI from a thread other than the UI-thread.

C#
// create a composite logger with all the loggers
CompositeLogger compositeLogger =
    new CompositeLogger(
            new TextBoxLogger(textBox),
            new ListBoxLogger(listBox),
            new FileLogger("C:\\LogPattern.txt"),
            new EventLogger());

// create a anonymous function to call DoSomthing
ParameterizedThreadStart threadDelegate = delegate(object obj)
{
    ILogger logger = (ILogger)obj;
    DoSomthing(logger);
};

// Do Somthing in a thread
Thread t = new Thread(threadDelegate);
t.Start(compositeLogger);

Conclusion

Notice that I am now able to pass an ILogger object to a method within the business layer or to the data layer, and provide an easy way to log a message. This is done by using the ILogger interface, and therefore, your business layer or data layer requires no extra references to File.Io, or Windows.Forms, it only needs to have a reference to ILogger. It is also nice that you can easily add and remove loggers by using your composite Logger. Thank you for reading, have a good day!

License

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


Written By
Web Developer
Canada Canada
I am currently working as a team leader with a group of amazing .NET programmers. I love coding with .NET, and I love to apply design patterns into my work. Lately I had some free time, so I decided to write some articles, hoping I will spare someone frustration and anxiety.

Comments and Discussions

 
GeneralI like it... Pin
BoneSoft25-Jul-06 4:12
BoneSoft25-Jul-06 4:12 

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

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.