Showing posts with label C# .net compoiste design patterns. Show all posts
Showing posts with label C# .net compoiste design patterns. Show all posts

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!