Click here to Skip to main content
15,885,960 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 94.3K   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

 
QuestionThank you Pin
RaghavendraSankar4-Sep-12 1:57
RaghavendraSankar4-Sep-12 1:57 
NewsMore Pin
mikeperetz16-Nov-07 1:43
mikeperetz16-Nov-07 1:43 
GeneralRe: More Pin
RickyJiao7-Nov-11 15:57
RickyJiao7-Nov-11 15:57 
GeneralAnother method ... Pin
David Catriel28-Aug-07 10:32
David Catriel28-Aug-07 10:32 
GeneralNice easy-going article Pin
David Catriel30-May-07 7:51
David Catriel30-May-07 7:51 
GeneralHi Pin
Subhash Balachandran6-Aug-06 3:48
Subhash Balachandran6-Aug-06 3:48 
GeneralNice example for composite pattern Pin
ColonelSender25-Jul-06 10:46
ColonelSender25-Jul-06 10:46 
GeneralI like it... Pin
BoneSoft25-Jul-06 4:12
BoneSoft25-Jul-06 4:12 
GeneralCompare to built in logging Pin
boo18-Jul-06 15:11
boo18-Jul-06 15:11 
GeneralWell done Pin
jonavi18-Jul-06 9:25
jonavi18-Jul-06 9:25 
GeneralRe: Well done Pin
mikeperetz18-Jul-06 10:20
mikeperetz18-Jul-06 10:20 
QuestionCombine with Provider? Pin
Mike Ellison18-Jul-06 6:46
Mike Ellison18-Jul-06 6:46 
AnswerRe: Combine with Provider? Pin
mikeperetz18-Jul-06 10:19
mikeperetz18-Jul-06 10:19 
AnswerRe: Combine with Provider? Pin
mikeperetz18-Jul-06 10:34
mikeperetz18-Jul-06 10:34 
QuestionWhy not log4net? Pin
MikeP_AIS18-Jul-06 5:51
MikeP_AIS18-Jul-06 5:51 
AnswerRe: Why not log4net? Pin
mikeperetz18-Jul-06 10:41
mikeperetz18-Jul-06 10:41 
GeneralRe: Why not log4net? Pin
boo18-Jul-06 15:10
boo18-Jul-06 15:10 
GeneralRe: Why not log4net? Pin
MikeP_AIS19-Jul-06 11:02
MikeP_AIS19-Jul-06 11:02 
GeneralRe: Why not log4net? Pin
boo19-Jul-06 12:53
boo19-Jul-06 12:53 
GeneralRe: Why not log4net? Pin
MikeP_AIS20-Jul-06 4:48
MikeP_AIS20-Jul-06 4:48 
Both Winforms and ASP.NET. The config file is the same. There are a couple of tricks with ASP.NET 2.0. Check out this site for details.

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.