Figure 1. DebugView
Figure 2. EventViewer
Figure 3. Log files
Figure 4. Console
Introduction
This article presents a quick-and-easy distributed application logger (known
as AppLogger), without all the frills and overheads that come with Microsoft's
Exception Management and Logging Application Blocks.
To run the demo, just unzip the demo files,
- start \AppLoggerDemo\AppLogger\AppLogger.exe
- then run \AppLoggerDemo\AppLoggerTest\AppLoggerTest.exe
Background
One of the most basic and fundamental component for any distributed system is
an application logger providing a networked logging service in which the
system's collaborating components can log errors or exceptions to.
Douglas C. Schmidt, a renown authority in real-time distributed systems
(especially in CORBA), presented a Networked Logging Service in his book "C++
Network Programming, Volume 1, Mastering Complexity with ACE and Patterns".
Needless to say, .NET Remoting (a close technology cousin to CORBA), is used
in the AppLogger solution presented in this article.
Features
AppLogger.exe is built in the solution's SystemFrameworks project. By
default, the project is built as console application to facilitate debugging. To
build it as Windows Service, all you need to do is to switch the project's
properties' "Start Object
" to
"AppLogger.SystemFrameworks.AppAsService
".
All built binaries are output to Assemblies project's /bin folder. I have to
warn you that there exists a Microsoft bug in which Visual Studio.NET 2003 is
unable to copy or save files to a common folder if your solution/projects get
too big. But AppLogger is a small application, so you will not hit this bug.
Remoting Server Configuration
AppLogger.exe's Remoting configuration(in App.Config) is set to "SingleCall",
TCP mode using port 20911. Its actual runtime logger is instantiated on startup,
and can be changed using the <appSettings> section. The four types of
logger which you can configure are:
DebugLogger |
Logs output to DebugView (a freeware available from http://www.sysinternals.com/ since the
days of Win32 and MFC). Refer to Figure 1 above. |
EventLogger |
Logs output to Windows Event Viewer. Refer to Figure 2
above. |
FileLogger |
Logs output to text file(s). Each source application has its own
file. Refer to Figure 3 above. |
ConsoleLogger |
Logs output to console. Refer to Figure 4 above. |
DbLogger |
Not implemented in this demo, but you can easily implement an ADO.NET class
under DataAccess project that logs output to
database. |
<appSettings>
-->
<add key="AppLogger.Type" value="AppLogger.BusinessRules.FileLogger" >
...
</appSettings>
Remoting Client Configuration
AppLoggerTest project is our test client and it only needs to have the
following configuration in its App.Config.
<appSettings>
-->
<add key="AppLogger.Home.Location"
value="tcp://localhost:20911/AppLogger.IHome.rem" />
...
</appSettings>
Using the code
AppLoggerHelper Class
The AppLoggerHelper
is a wrapper class in AppLogger's
BusinessFacade
project. It provides an easy API for logging
exception, error, warning, information or debug messages. It hides the Remoting
and log message complexity from the caller.
public class AppLoggerHelper
{
private static string s_strLocationHome =
ConfigurationSettings.AppSettings["AppLogger.Home.Location"];
private AppLoggerHelper()
{
}
public static void LogException(Exception ex)
{
...not shown for simplicity...
}
public static void LogError(string strMsg)
{
...not shown for simplicity...
}
public static void LogWarning(string strMsg)
{
...not shown for simplicity...
}
public static void LogInfo(string strMsg)
{
...not shown for simplicity...
}
public static void LogDebug(string strMsg)
{
#if DEBUG
...not shown for simplicity...
#endif
}
}
The following are simple test codes in AppLoggerTest project. It uses the
helper class for logging.
public class AppLoggerTest
{
public void TestDebugLog()
{
AppLoggerHelper.LogDebug("This is debug log.");
}
public void TestInfoLog()
{
AppLoggerHelper.LogInfo("This is info log.");
}
public void TestWarningLog()
{
AppLoggerHelper.LogWarning("This is warning log.");
}
public void TestErrorLog()
{
AppLoggerHelper.LogError("This is error log.");
}
public void TestExceptionLog()
{
try
{
throw new ApplicationException(
"This is a simulated exception.");
}
catch (Exception ex)
{
AppLoggerHelper.LogException(ex);
}
}
public static void Main()
{
AppLoggerTest objTester = new AppLoggerTest();
objTester.TestDebugLog();
objTester.TestInfoLog();
objTester.TestWarningLog();
objTester.TestErrorLog();
objTester.TestExceptionLog();
Console.WriteLine("Tests completed.");
}
}
Points of Interest
The AppLogger uses a typical Adapter pattern common to CORBA solutions by
specifying interfaces (as in Definition Language - IDL). The BusinessFacade's
IHome
interface returns an IAppLogger
interface. In my
other projects, I often use a home interface to house all my other published
interfaces. (In the CORBA world, there are more than one way to publish
interfaces, but that's not the scope of this article.)
Instead of inventing another application framework, I have decided to use the
BusinessFacade, BusinessRules, DataAccess and SystemFrameworks layers generated
by Visual Studio's "Simple Distributed Application" project template. For a good
treatment of the advantages of using a layered framework, you can refer to the
"Best Practices" available in MSDN, or any good architecture books describing
the "Microkernel" architecture pattern.
The IAppLogger
interface exposes just one simple remote method,
LogMessage(...)
, that accepts a LogMessage
object as
argument. Users of this method is provided an even more straight-forward API,
called AppLoggerHelper
class, as described above.
Clients of AppLogger need only to reference the
AppLogger.BusinessFacade.dll. Any change in BusinessRules and/or
DataAccess layers will have no impact to clients. This is clearly demonstrated
since AppLogger allows you to change its runtime logger type to
ConsoleLogger
, EventLogger
, DebugLogger
and FileLogger
. You can even implement (in the BusinessRules and
DataAccess layers) a DbLogger that persists logs to database without having to
change a single line of client codes.
Logging should be a short and fast action. Internal to AppLogger's
BusinessRules, logging is performed by DefaultAppLogger
class
asynchronously using .NET's ThreadPool
class. A point to
note is that we can further tweak AppLoggerHelper
to only log
errors and exceptions during production.
In part 2 of this article, we will explore how to "refactor" AppLogger to use
another asynchronous solution - MSMQ, which not only provides fast logging, but
"guaranteed" delivery of messages. In the mean time, I hope you enjoy using
AppLogger and keep your suggestions flowing.
Rock on!