Click here to Skip to main content
Click here to Skip to main content

A Façade for Simple and Framework-Independent Logging in .NET

By , 2 Dec 2009
Rate this:
Please Sign up or sign in to vote.

Download hardcodet-logging.zip - 234.61 KB  

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 maintaing the goal of simplicity. You can read about it here:  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 is the facade. Behind the scenes,
//a framework of your choice is used
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.

 

image-thumb1.png

(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:

//create a new logger instance
string file = @"C:\logfile.txt";
ILogger logger = BitFactoryLogger.CreateSingleFileLogger(file);

//use the global LoggerService to store the logger
LoggerService.SetLogger(logger);

...

//this will store the info in the log file
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 is the most verbose version
  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 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:

//init IOC container builder
var builder = new ContainerBuilder();

//register single logger instance
ILogger logger;

#if (DEBUG)
  logger = new ConsoleLogger();
#else
  logger = BitFactoryLogger.CreateSingleFileLogger(AppUtil.LogFile);
#endif

//register logger
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:

 image17.png

public void InitApp()
{
  //create a file logger (use BitFactory facade)
  string logFile = @"C:\MyLogFile.txt";
  ILogger logger = BitFactoryLogger.CreateSingleFileLogger(logFile);

  //register as globally used logger
  LoggerService.SetLogger(logger);
}

private void Foo()
{
  try
  {
    DoSomethingWrong();
  }
  catch(Exception e)
  {
    //get registered logger and log exception
    ILogger logger = LoggerService.Logger;
    logger.Log(e);
  }
} 

A nice thing about LoggerService: It guarantees you always 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
{
  /// <span class="code-SummaryComment"><summary>
</span>  /// Provides a global repository for a given <span class="code-SummaryComment"><see cref="ILogger"/>
</span>  /// instance. This class ensures that the <span class="code-SummaryComment"><see cref="Logger"/>
</span>  /// property is never nullo - in case no logger is defined, it
  /// automatically installs a <span class="code-SummaryComment"><see cref="NullLogger"/>
</span>  /// instance.
  /// <span class="code-SummaryComment"></summary>
</span>  public static class LoggerService
  {
    private static ILogger logger = new NullLogger();

    /// <span class="code-SummaryComment"><summary>
</span>    /// Gets the installed <span class="code-SummaryComment"><see cref="ILogger"/> implementation.
</span>    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><remarks>This property always returns a valid
</span>    /// logger.<span class="code-SummaryComment"></remarks>
</span>    public static ILogger Logger
    {
      get { return logger; }
    }

    /// <span class="code-SummaryComment"><summary>
</span>    /// Installs a given logger or resets the <span class="code-SummaryComment"><see cref="Logger"/>
</span>    /// to a <span class="code-SummaryComment"><see cref="NullLogger"/> instance if the
</span>    /// <span class="code-SummaryComment"><paramref name="loggerImplementation"/> is a null
</span>    /// reference.
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><param name="loggerImplementation">The logger to be
</span>    /// used globally, or a null reference in order to reset
    /// the service.<span class="code-SummaryComment"></param>
</span>    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
{
  /// <span class="code-SummaryComment"><summary>
</span>  /// A very simple implementation of <span class="code-SummaryComment"><see cref="ILogger"/>
</span>  /// that outputs all messages to the system console.
  /// <span class="code-SummaryComment"></summary>
</span>  public class ConsoleLogger : LoggerBase
  {
    /// <span class="code-SummaryComment"><summary>
</span>    /// Logs a given item to the console.
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><param name="item">The item to be logged.</param>
</span>    /// <span class="code-SummaryComment"><exception cref="ArgumentNullException">If <paramref name="item"/>
</span>    /// is a null reference.<span class="code-SummaryComment"></exception>
</span>    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
{
  /// <span class="code-SummaryComment"><summary>
</span>  /// An implementation of the <span class="code-SummaryComment"><see cref="ILogger"/>
</span>  /// interface which outputs logged data using
  /// the <span class="code-SummaryComment"><see cref="Logger"/> of the MS Enterprise
</span>  /// Library.
  /// <span class="code-SummaryComment"></summary>
</span>  public class EnterpriseLibraryLogger : LoggerBase
  {

    /// <span class="code-SummaryComment"><summary>
</span>    /// Writes a log entry to the Enterprise Library's
    /// logging block. Output depends on the logging
    /// block's configuration.
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><param name="item">An log item which encapsulates
</span>    /// information to be logged.<span class="code-SummaryComment"></param>
</span>    public override void Log(LogItem item)
    {
      LogEntry entry = ConvertLogItem(item);
      Logger.Write(entry);
    }

    /// <span class="code-SummaryComment"><summary>
</span>    /// Creates a <span class="code-SummaryComment"><c>LogEntry</c> instance which can be processed
</span>    /// by the Enterprise Library based on a given log item. 
    /// <span class="code-SummaryComment"></summary>
</span>    /// <span class="code-SummaryComment"><param name="item">An log item which encapsulates information
</span>    /// to be logged.<span class="code-SummaryComment"></param>
</span>    /// <span class="code-SummaryComment"><returns>An Enterprise Library item which corresponds
</span>    /// to the submitted <span class="code-SummaryComment"><c>LogItem</c>.</returns>
</span>    private static LogEntry ConvertLogItem(LogItem item)
    {
      //assign properties
      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 :-)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Philipp Sumi
Architect I'm a gun for hire
Switzerland Switzerland
Philipp is an independent software engineer with great love for all things .NET.
He lives in Winterthur, Switzerland and his home on the web is at http://www.hardcodet.net.

Comments and Discussions

 
GeneralMy vote of 5 PinmemberOshtri Deka13-Apr-11 1:57 
QuestionCould you provide me with some event viewer logging sample code please? PinmemberWillem Le Roux29-Jul-09 7:27 
GeneralThis is why we wrote the Blog aggregator PinadminChris Maunder15-May-09 13:43 
GeneralRe: This is why we wrote the Blog aggregator PinmemberPhilipp Sumi15-May-09 13:46 
GeneralNice work [modified] PinmemberColin Eberhardt15-May-09 4:12 
GeneralRe: Nice work PinmemberPhilipp Sumi15-May-09 7:34 
GeneralRe: Nice work PinmemberColin Eberhardt17-May-09 21:45 
GeneralRe: Nice work PinmemberPhilipp Sumi17-May-09 21:54 
GeneralRe: Nice work PinmemberColin Eberhardt19-May-09 3:52 
GeneralRe: Nice work PinmemberPhilipp Sumi19-May-09 21:10 
GeneralPerformance and the [Conditional] Attribute PinmemberJaredThirsk1-Jul-10 19:56 
Jokeeasy+nice (dead easy ^^) Pinmemberdatacore15-May-09 3:00 
GeneralRe: easy+nice (dead easy ^^) PinmemberPhilipp Sumi15-May-09 3:02 
QuestionWhy invent another logger? PinmemberRobert Taylor14-May-09 22:18 
AnswerRe: Why invent another logger? [modified] PinmemberPhilipp Sumi14-May-09 22:31 
GeneralRe: Why invent another logger? PinmemberRobert Taylor14-May-09 23:07 
GeneralRe: Why invent another logger? PinmemberTrent Tobler22-May-09 5:33 
GeneralRe: Why invent another logger? PinmemberPhilipp Sumi22-May-09 10:04 
GeneralRe: Why invent another logger? PinmemberTrent Tobler25-May-09 13:40 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web02 | 2.8.140415.2 | Last Updated 3 Dec 2009
Article Copyright 2009 by Philipp Sumi
Everything else Copyright © CodeProject, 1999-2014
Terms of Use
Layout: fixed | fluid