Monday, November 12, 2007

Logging Using the Composite 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.

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.

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

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

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

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:

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:

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

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

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:

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.

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.

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





2 comments:

Anonymous said...

Good information,

but your Download link seems to be broken on CodePorject...

Thanks

Jim

Anonymous said...

Who knows where to download XRumer 5.0 Palladium?
Help, please. All recommend this program to effectively advertise on the Internet, this is the best program!