Figure 1. Computer Management console screen-shot
Introduction
A quick-and-easy distributed application logger (AppLogger) was introduced in Part 1 of this article. TCP Remoting was used as the underlying protocol that makes AppLogger "distributed". The use of BusinessFacade, BusinessRules, DataAccess and SystemFrameworks layers was also presented as a way to structure your application. It was also mentioned that logging should be a short-and-fast action, i.e. the caller should never be in a blocking state. "Asynchronization" was implemented server-side in AppLogger's BusinessRule layer.
In this article, we will "refactor" AppLogger to make it even more "asynchronous". In fact, we want to make the client-side calls (via AppLoggerHelper
APIs) to be asynchronous via some sort of one-way messaging mechanism. Again, instead of creating a message queuing mechanism from scratch, we will apply some pragmatism. We will utilize the MSMQ provided by the Operating System(Windows XP, Windows 2000, Windows 2003) itself. Note that MSMQ is not automatically activated when you install your OS. You will have to add it yourself in "Control Panel->Add/Remove Programs->Add/Remove Windows Components".
To run the demo, just unzip the demo files, and do the following:
- from Control Panel->Administrative Tools->Computer Management, select Message Queuing, then add a Private Queue called "applogger". (Refer to Figure 1 above)
- start \AppLoggerDemo2\AppLogger\AppLogger.exe
- then run \AppLoggerDemo2\AppLoggerTest\AppLoggerTest.exe
Background
In an ideal world, application error logging should be an exception and not the norm. If there are so many errors and exceptions to log, your applications are already functioning in degraded mode. However, this world is less than ideal (as we all know it), and more often than not we even need to log debug messages just to keep us happy that things are running fine.
So in order not to "degrade" our AppLogger, we want to make the APIs provided by AppLoggerHelper asynchronous. In essence, users of AppLoggerHelper will effectively "enqueue" their log messages and AppLogger will "dequeue" and forward them to the concrete IAppLogger. As usual, we will touch base with a few design patterns along the way.
Features
The following has been added to the projects in AppLogger solution:
BusinessFacade |
Reference to System.Messaging.dll added. |
|
MsmqHelper class added. |
|
ProxyMsmqHome class added. |
|
ProxyMsmqLogger class added. |
|
|
BusinessRules |
Reference to System.Messaging.dll added. |
|
MsmqObserver class added. |
|
MsmqLogger class added. |
The MsmqHelper
class provides helper methods to open a queue from MSMQ. The ProxyMsmqHome
(a concrete IHome
) and ProxyMsmqLogger
(a concrete IAppLogger
), as the name implied, is nothing but a Proxy classes. "The Proxy design pattern makes the clients of a component communicate with a representative rather than the component itself" - Frank Buschmann et. al., from their book "Pattern-Oriented Software Architecture" (otherwise known as the POSA book). As it turns out, MSMQ is just another "transport" protocol we need to set in our App.Config files for both client(AppLoggerTest.exe) and server(AppLogger.exe).
AppLogger.exe Configuration
AppLogger.exe's App.Config has a new key call "AppLogger.MSMQ". Note that "bordeaux" is my machine name, change it to your machine name (not necessarily "chablis", just joking...).
<appSettings>
-->
<add key="AppLogger.Type"
value="AppLogger.BusinessRules.ConsoleLogger" >
...
-->
<add key="AppLogger.MSMQ"
value="FormatName:DIRECT=OS:bordeaux\private$\applogger" />
</appSettings>
Client Configuration
In AppLoggerTest's App.Config, only the value of the key "AppLogger.Home.Location" needs to be changed to using "msmq:" protocol.
<appSettings>
-->
<add key="AppLogger.Home.Location"
value="msmq:FormatName:DIRECT=OS:bordeaux\private$\applogger" />
...
</appSettings>
Using the code
MsmqObserver
and MsmqLogger
Classes
As the name implies, MsmqObserver
observes for (or listens to) messages being sent to the physical "applogger" queue in MSMQ. The MsmqLogger, which is a Singleton and also implements the IAppLogger
interface, will subscribe for notifications (via callback method MsmqLogger.NotifyMe(...)
) with MsmqObserver
. In short, MsmqLogger
is asking MsmqObserver
to notify it when a message is received from MSMQ. This typical Observer pattern is well explained in Gang-Of-Four's "Design Patterns" book. The MsmqLogger
Singleton is started when AppLogger.exe starts up.
At this junction, it is good to point out that before the jargon "Design Patterns" became common-speak, there is such a thing call "Idioms", phrased by James O. Coplien, author of a highly-rated book "Advanced C++ Programming Styles and Idioms". The MsmqLogger
, upon notification of log message arrival, forwards the message to the real logger (the real logger is an already implemented concrete IAppLogger
that knows where to persist the log message to). This typifies the "Handle/Body" idiom or so-called Bridge design pattern.
public class MsmqLogger : IAppLogger
{
private static MsmqLogger m_objInstance = new MsmqLogger();
private MsmqObserver m_objMsmqObserver;
private IAppLogger m_objRealLogger;
private MsmqLogger()
{
Setup();
}
private void Setup()
{
...not shown for simplicity...
}
private void NotifyMe(object obj)
{
System.Messaging.Message objRawMsg = (System.Messaging.Message)obj;
AppLogger.BusinessFacade.LogMessage objActualMsg =
(AppLogger.BusinessFacade.LogMessage)objRawMsg.Body;
LogMessage(objActualMsg);
objActualMsg = null;
objRawMsg = null;
}
public static MsmqLogger GetInstance()
{
return m_objInstance;
}
public void LogMessage(LogMessage objMessage)
{
m_objRealLogger.LogMessage(objMessage);
}
...
}
And finally, thanks to the Proxy pattern, there is no client-side code changes in AppLoggerTest.
Conclusion
This poor man's distributed application logger can be deployed at multiple application servers. However, in the real world, it is quite a nightmare to keep multiple error log files or to search through multiple Windows Event logs in multiple machines. As mentioned in Part 1 of this article, you can certainly provide a DbLogger (in BusinessRules and DataAccess layers) which persists log messages to your database. Once stored in database, tracing, searching and filtering will just be a matter of SQL statements.
It seems that this "economy-class" logging application had taken us not only to design patterns lands, but also accommodated our refactored designs. I hope you enjoy reading as much as I enjoy writing this article. Again, keep your suggestions flowing and Rock On!