Hiding loggers implementations using Unity

Loggers are one of most popular examples about interfaces for sure. And there are a lot of implementations of loggers. Some implementations are simple and yet powerful, some implementations may be more complex. All we have to is to select implementation we need and integrate it to our application. It seems like good idea at first place but as soon as we need to switch from one implementation to another we discover nasty dependencies we have to change in all places where we are logging. Let’s see how to avoid these dependencies.

Analysis

To keep our application independent from logger implementation we need an interface that all our loggers will use. Our own loggers can implement our interface directly and for third-party loggers we can write wrapper classes that implement our interface. This way or other we logger interface.

Besides errors we may want to write also other messages to error log. We may need informative messages, warnings and debugging messages. To support different message types we need an enumerator.

Design

Our analysis gave us two objects: interface for loggers and enumerator for log message types. Our logger class looks like this.

ILogger interface

In code we have ILogger interface and LogEntryType enumerator.


public interface ILogger
{
   
void Log(LogEntryType type, string
message);
   
void Log(Exception
exception);
}

public enum LogEntryType
{
    Info,
    Warning,
    Error
}

LogEntryType is very simple here because it is for example purposes. In real life this enum may be way longer than here. Good example is TraceSeverity enum from my blog postSharePoint: Writing messages to ULS (Unified Logging System).

Implementing ILogger

Let’s create now two logger implementations. One of these classes is our own logger class and the other one will be wrapper for log4net logger.

DebugLogger

DebugLogger is simple logger that writes all log messages to console. It does nothing special – it writes all the output to debug window.


public class DebugLogger : ILogger
{
   
public void Log(LogEntryType entryType, string
message)
    {
       
var
logMessage = GetLogMessage(entryType, message);
       
Debug
.WriteLine(logMessage);
    }

   
public void Log(Exception
ex)
    {
       
var logMessage = GetLogMessage(LogEntryType
.Error, ex.ToString());
       
Debug
.WriteLine(logMessage);
    }

   
private string GetLogMessage(LogEntryType entryType, string
message)
    {
       
var messageBuilder = new StringBuilder
();
        messageBuilder.Append(DateTime.Now.ToLongTimeString());
        messageBuilder.Append(
" "
);
        messageBuilder.Append(
Enum.GetName(typeof(LogEntryType
), entryType));
        messageBuilder.Append(
" "
);
        messageBuilder.Append(message);
       
return messageBuilder.ToString();
    }
}

Output of DebugLogger is shown on following screenshot.

Debug logger output

Log4NetLogger

Log4NetLogger is simple wrapper class. It only writes messages to log4net log. All other details are handled by log4net.


public class Log4NetWrapper : ILogger
{
   
private static readonly ILog log = LogManager.GetLogger(typeof(Log4NetWrapper
));

   
static
Log4NetWrapper()
    {
       
XmlConfigurator
.Configure();
    }

   
public void Log(LogEntryType entryType, string
message)
    {
       
lock
(log)
        {
           
switch
(entryType)
            {
               
case LogEntryType
.Error:
                    log.Error(message);
                   
break
;
               
case LogEntryType
.Warning:
                    log.Warn(message);
                   
break
;
               
default
:
                    log.Info(message);
                   
break
;
            }
        }
    }

   
public void Log(Exception
ex)
    {
       
lock (log)
        {
            log.Error(ex);
        }
    }
}

In the case of file output we get result like this.

log4net wrapper output

Breaking dependencies

Now it’s time to add logging support to our application. All logging clients must see only ILogger interface and LogEntryType enum. Plus one thing more – they have to know how to get logger. We will use Unity as IoC container. We want to keep information about mappings in application configuration file. To make unity read this configuration we need some place where we can initialize Unity. Look at Main() method (and Unity configuration) from my blog entry Unity and singletons (it’s not long). Now, if we need some logging we can ask logger simply by writing the following code.


var logger = container.Resolve<ILogger>();

To keep Unity logic away from your classes you can use some wrapper class that uses Unity IoC container internally. Also it is possible to write generic wrapper for IoC containers so your code knows only one concrete IoC wrapper class. In this case IoC implementation details are hidden for your client code.


Leave a Reply

Your email address will not be published. Required fields are marked *