Before you start reading: Based on this project, we started (and released) SLF, the Simple Logging Façade. SLF is a bit bigger than this project, but much more flexible, while maintaining the goal of simplicity. You can read about it at http://slf.codeplex.com.
Introduction
Logging is an important aspect, but I don’t like to have dependencies on a specific logging framework all over the place. This is where a logging façade comes in handy. Basically, a façade just provides you with a common interface
that decouples the used logging framework from your code:
ILogger logger = LoggerService.Logger;
logger.Log("hello world");
The idea of a logging façade isn’t exactly new, but I thought I’d share this one with you for a few reasons:
- It is dead easy to use.
- It provides quite a few overloads when it comes to logging.
- The core library has no dependencies on other libraries at all.
- There are two façades (separate DLLs) that log through the Enterprise Library Logging block or the BitFactory logger. And hopefully more to come!
- Writing your own façade is as simple as overriding one single method.
- It’s not part of another project (completely standalone), and there is no license attached to it. Do with it whatever you want.
- It is dead easy to use.
(click on image to enlarge)
Here’s one way to create a file-based logger (using the BitFactory façade) and make it globally accessible. This only takes you a few lines of code:
string file = @"C:\logfile.txt";
ILogger logger = BitFactoryLogger.CreateSingleFileLogger(file);
LoggerService.SetLogger(logger);
...
LoggerService.Logger.Log("This is an information");
Logging via ILogger
The whole purpose of this project is to shield your libraries from the actually chosen logging framework. Accordingly, you are always logging through the ILogger
instance. ILogger
provides quite a few overloads of the Log
method, here are a few of them:
public void LogData(ILogger logger)
{
logger.Log("An information");
logger.Log("Something Happened", TraceEventType.Warning);
LogItem item = new LogItem();
item.Message = "My Message";
item.EventId = 999;
item.Categories.Add("Foo");
item.Priority = 10;
logger.Log(item);
try
{
DivideByZero();
}
catch(Exception e)
{
logger.Log(e);
logger.Log("Additional message.", e);
logger.Log("Additional message.", e, TraceEventType.Critical);
}
}
Initializing an ILogger Implementation
During the initialization of your application, you will have to specify the logger implementation that is supposed to be used. This might happen declaratively or directly in code. Here’s the initialization code from NetDrives, which makes a logger available through the AutoFac IOC container.
Note that I’m registering a ConsoleLogger
for debug builds, while release builds write into a log file. These are completely different classes, but it doesn’t matter - they both implement the ILogger interface
:
var builder = new ContainerBuilder();
ILogger logger;
#if (DEBUG)
logger = new ConsoleLogger();
#else
logger = BitFactoryLogger.CreateSingleFileLogger(AppUtil.LogFile);
#endif
builder.Register(logger).As<ILogger>();
Registration and Access through LoggerService
I prefer to initialize and access my logger through an IOC container, but you can do it however you like. If you’re lacking a place to make your ILogger
globally accessible, you can use the static LoggerService
class:
public void InitApp()
{
string logFile = @"C:\MyLogFile.txt";
ILogger logger = BitFactoryLogger.CreateSingleFileLogger(logFile);
LoggerService.SetLogger(logger);
}
private void Foo()
{
try
{
DoSomethingWrong();
}
catch(Exception e)
{
ILogger logger = LoggerService.Logger;
logger.Log(e);
}
}
A nice thing about LoggerService
: It always guarantees you a valid ILogger instance
. If no logger is set, it just falls back to a NullLogger
implementation that does not create any output at all. Here’s the implementation:
namespace Hardcodet.Util.Logging
{
public static class LoggerService
{
private static ILogger logger = new NullLogger();
public static ILogger Logger
{
get { return logger; }
}
public static void SetLogger(ILogger loggerImplementation)
{
logger = loggerImplementation ?? new NullLogger();
}
}
}
Creating a New Logger Façade
In case you want to use another logging framework (e.g., NLog or Log4Net), creating a new façade is very easy. Basically, you create a new project, set a reference to the base library and write a class that either:
- implements
ILogger
directly
- or, even simpler, derives from the
abstract LoggerBase
class.
Feel like sharing your own façade? Just contact me and I’ll happily include your implementation.
As a sample, here’s the code of the ConsoleLogger
(part of the core library) and the Enterprise Library façade:
using System;
namespace Hardcodet.Util.Logging
{
public class ConsoleLogger : LoggerBase
{
public override void Log(LogItem item)
{
if (item == null) throw new ArgumentNullException("item");
Console.Out.WriteLine(item.ToLogMessage());
}
}
}
using Microsoft.Practices.EnterpriseLibrary.Logging;
namespace Hardcodet.Util.Logging.EntLibFacade
{
public class EnterpriseLibraryLogger : LoggerBase
{
public override void Log(LogItem item)
{
LogEntry entry = ConvertLogItem(item);
Logger.Write(entry);
}
private static LogEntry ConvertLogItem(LogItem item)
{
LogEntry entry = new LogEntry();
entry.Message = item.Message;
entry.Title = item.Title;
entry.AddErrorMessage(item.ErrorMessage);
entry.EventId = item.EventId;
entry.Priority = item.Priority;
entry.Severity = item.Severity;
entry.TimeStamp = item.TimeStamp;
foreach (string category in item.Categories)
{
item.Categories.Add(category);
}
return entry;
}
}
}
The download contains the core library, two external façades (BitFactory, Enterprise Library), and a sample project. Hope you’ll like it!