Home > SLF > SLF Hands-on Tutorial, Part 1

SLF Hands-on Tutorial, Part 1

December 2nd, 2009

 slf 

This is an introductory tutorial on SLF, the Simple Logging Façade. This tutorial covers the basics, which will be all you’ll ever need in most projects. I’m planning on writing a second tutorial that will show custom factories and resolvers soon.

Downloading SLF

To get started, visit http://slf.codeplex.com and download the latest release. Source code is available as a Visual Studio 2008 solution, but if you’re working with VS2005, there’s also precompiled binaries for .NET 2.0 available (.NET 1.0 or 1.1 is not supported).

Sample Projects

SLF comes with a lot of samples, all organized as independent projects that discuss a specific use case. As you will see, most scenarios only require a few lines of code. We are planning to extend this section over time, based on your feedback.

 

image

 

ILogger

The central interface in SLF is ILogger. ILogger provides a versatile API to log information to a specific logging framework. You could roughly say there’s three “categories” of loggers:

  1. A few simple implementations directly in the SLF core library (e.g. ConsoleLogger or DelegateLogger) that cover common scenarios.
  2. Façades for logging frameworks that are currently supported (e.g. log4net). A façade is usually an independent DLL.
  3. Custom loggers that you implement yourself. You can do that very easily, usually by extending a simple base class. Creating fancy custom loggers will be covered in the second part of this tutorial.

image

 

Here’s a first snippet, that creates a ConsoleLogger, which is part of the core library:

ILogger logger = new ConsoleLogger(); 
logger.Info("hello world");

 

This produces the following output

image

 

However, rather than creating your loggers all over the place, you will usually use LoggerService to obtain your logger(s):

ILogger logger = LoggerService.GetLogger(); 
logger.Info("hello world");

 

Setting up logging with LoggerService is described in a bit further below.

 

Logging in Categories

As a general guideline on how and when to use logging categories, I recommend Colin Eberhardt’s “The Art of Logging”, which is an excellent introduction to logging in general.

SLF knows 5 logging categories (or log levels), which are covered through a set of methods that allow you to easily submit logging information. 

Logging Level ILogger Methods
Info Info()
Warn Warn()
Error Error()
Fatal Fatal()
Debug Debug()

Every one of these methods provides several overloads, which allow you to submit simple text, exceptions, or even format text on the fly. A few exemplary logging instructions:

ILogger logger = new ConsoleLogger();

logger.Info("Simple Message."); 
logger.Info("User {0} logged in at {1}", GetUserName(), DateTime.Now); 
logger.Warn(e, "Message for uncritical exception."); 
logger.Error(someException); 
logger.Fatal(e, "Login exception for user {0}", GetUserName());

 

Logging a LogItem

Apart from the the above mentioned Info(), Warn(), Error(), Fatal() and Debug() methods, there’s also the Log method, which expects an parameter of type LogItem. LogItem is a simple class that encapsulates information that belongs to a given log entry:

 

image

 

Working with LogItem is not as straightforward as using the other methods, but it may be useful if you want to submit more detailed logging information. Here’s a sample that logs a warning along with an event ID:

public void Login(User user) 
{ 
   //do the login 
   LoginResult result = ...


   //log an Info message with a title and an event ID 
   LogItem item = new LogItem(); 
   item.LogLevel = LogLevel.Warn; 
   item.EventId = 201; 
   item.Title = "Invalid login attempted by: " + user.Name; 
   item.Message = result.ToDetailedString(); 
    
   ILogger logger = ...
   logger.Log(item); 
}

 

 

Setting up Logging in Code

 

Do it Yourself: Storing a Reference to ILogger

Basically, the simplest possible implementation to use SLF is to just store an ILogger instance somewhere and be on your way. The sample below uses the BitFactory logger façade, which internally forwards log messages to the BitFactory logging framework:

public static class MyApplication 
{
  //there’s a static convenience method that creates a logger for a given file 
  private ILogger logger = BitFactoryLogger.CreateFileLogger("log.txt");

  public static ILogger Logger 
  { 
    get { return logger; }    
  }
}

 

However, we recommend to use LoggerService instead:

LoggerService

LoggerService is a static repository you can use in different ways. It provides support for declarative configurations (taken from app.config), lets you just plug in a single logger, or completely customize and plug in advanced logger resolution strategies.

Lets have a look at a simplistic example: Create the above ConsoleLogger and make it available through LoggerService. Again, this is as simple as it gets:

ILogger logger = new ConsoleLogger(); 
LoggerService.SetLogger(logger);

 

With a logger plugged in like this, we can use LoggerService from anywhere in our application to get a hold of the ConsoleLogger we just plugged in:

ILogger logger = LoggerService.GetLogger(); 
logger.Info("hello world");

 

Good to Know: LoggerService Never Returns Null

LoggerService guarantees you to always return a valid ILogger instance. If no logger is configured, it will just return you a NullLogger instance. NullLogger is a special implementation of the ILogger interface, which just discards everything you throw at it. You could say that NullLogger switches off logging without requiring any changes in your code.

 

Thanks to NullLogger, you never have to check for null references when retrieving a logger. Look at the snippet below: This code will not result in an exception, because GetLogger will not return null, but a NullLogger instance:

//you can explicitly assign a null reference… 
LoggerService.SetLogger(null); 

//…and the service will return you a NullLogger 
ILogger logger = LoggerService.GetLogger();

//NullLogger will just discard this message, so no real logging happens
logger.Info("This message will be discarded"); 

 

Setting up Logging Declaratively (App.config)

Per default, LoggerService analyzes the application’s configuration file (app.config) in order to detect configured loggers. So if you want to go the declarative route, you don’t have to write a single line of code – it just works.

Here’s a sample app.config file that makes SLF write anything to the console through a ConsoleLogger:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="slf" type="Slf.Config.SlfConfigurationSection, slf"/>
  </configSections>

  <slf>
    <factories>
      <!-- log everything through the built-in ConsoleLogger -->
      <factory type="Slf.Factories.ConsoleLoggerFactory, SLF" />
    </factories>
  </slf>

</configuration>

 

Declarative configurations often make sense in more complex scenarios, especially if you use more powerful logging frameworks such as log4net. Here’s a complete configuration file that makes SLF use log4net. This app.config file contains two sections:

  • The slf section tells SLF to forward all logging data to log4net.
  • The log4net section is a standard log4net configuration (it has nothing to do with SLF). In this example, log4net is configured to write everything into a single log file.
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>
    <section name="log4net"
             type="log4net.Config.Log4NetConfigurationSectionHandler,log4net"/>
    <section name="slf"
             type="Slf.Config.SlfConfigurationSection, slf"/>
  </configSections>

  <slf>
    <factories>
      <!-- configure single log4net factory, which will get all logging output -->
      <factory type="SLF.Log4netFacade.Log4netLoggerFactory, SLF.Log4netFacade"/>
    </factories>
  </slf>


  <!-- configures log4net to write into a local file called "log.txt" -->
  <log4net>
    <appender name="MainAppender" type="log4net.Appender.FileAppender">
      <param name="File" value="log.txt" />
      <param name="AppendToFile" value="true" />
      <layout type="log4net.Layout.PatternLayout">
        <conversionPattern value="%date - %message %newline" />
      </layout>
    </appender>
    <root>
      <level value="ALL" />
      <appender-ref ref="MainAppender" />
    </root>
  </log4net>

</configuration>

 

If you configure SLF through app.config, you don’t have to write any code, because SLF will detect the configuration on its own. Accordingly, just get the logger through LoggerService and log away:

ILogger logger = LoggerService.GetLogger(); 
logger.Info("Application started at {0}", DateTime.Now);

 

Do note that if you plug in a custom logger through LoggerService.SetLogger, this will override any declaratively configured loggers. You can, however, always reset LoggerService through the LoggerService.Reset method.

 

Working With Loggers and Façades

 

Using the Built-in Loggers

SLF comes with a number of built-in loggers that implement ILogger, which you can use to quickly add logging functionality to your application. There’s the usual suspects such as ConsoleLogger, DebugLogger, or TraceLogger, as well as a few more fancy implementations (e.g. DelegateLogger or DecoratorLogger) and base classes you can use to quickly create your own logger implementations.

There’s a lot of samples that demonstrate the loggers and possibilities, all wrapped into individual projects. However, here’s a few samples:

 

Delegating Logging through DelegateLogger

DelegateLogger is a simple logger that allows you to quickly plug in some custom logic through a delegate or lambda expression. Here’s dummy sample for a WinForms application, which just shows a message box for every item that is logged to SLF.

DelegateLogger logger = new DelegateLogger(ShowMessage); 

//this message is being invoked by the logger 
private void ShowMessage(LogItem item) 
{ 
  MessageBox.Show("Logged Message: " + item.Message);
}

 

…or even shorter, with a Lambda Expression:

var logger = new DelegateLogger(item => MessageBox.Show(item.Message));

 

 

Combining Logged Output via CompositeLogger

CompositeLogger is a logger that does not log on its own, but forwards all logging information to an arbitrary number of other loggers:

CompositeLogger compositeLogger = new CompositeLogger();

compositeLogger.Loggers.Add(new ConsoleLogger); 
compositeLogger.Loggers.Add(new TraceLogger); 
compositeLogger.Loggers.Add(new DebugLogger);

compositeLogger.Info("Logging to all three loggers at once!");

 

You can also submit the underlying loggers directly as constructor parameters:

ILogger consoleLogger = new ConsoleLogger(); 
ILogger traceLogger = new TraceLogger(); 
ILogger debugLogger = new DebugLogger();

ILogger composite = new CompositeLogger(consoleLogger, traceLogger, debugLogger);

 

 

Formatting Logging Output

The first snippet in this tutorial wrote “hello world” to the console, which looked like this:

image

Now, a lot of loggers derive from FormattableLoggerBase. This base class provides a Formatter property, which takes an instance of ILogItemFormatter. ILogItemFormatter is a very simple interface, which has only one task: Converting a given LogItem into a string.

Accordingly, if you wanted to change the format of the above message, you could just write a custom formatter and assign it to your logger. Here’s an alternative for a simple XmlFormatter:

public class XmlFormatter : ILogItemFormatter
{
  private XmlSerializer serializer = new XmlSerializer(typeof(LogItem));

  public string FormatItem(LogItem item)
  {
    StringWriter writer = new StringWriter();
    serializer.Serialize(writer, item);
    return writer.ToString();
  }
}

 

Accordingly, we can plug in this formatter into our ConsoleLogger like this:

IFormattableLogger logger = new ConsoleLogger(); 
logger.Formatter = new XmlFormatter(); 
logger.Info("hello world");

 

The above snippet produces the following output, that renders a LogItem as an XML element:

image

 

Directing Trace Output To SLF

If you have an existing codebase that logs through the Trace class, and you would like to make use of SLF, you don’t necessarily have to change the existing code. The SLF core library contains the SlfTraceListener class which just redirects trace messages to SLF.

SLF comes with a sample that shows how to declare SlfTraceListener declaratively via app.config. But you can do this also programmatically:

static void Main(string[] args) 
{ 
  //add SLF listener to Trace 
  Trace.Listeners.Add(new SlfTraceListener()); 

  //write to Trace – this message will be redirected to SLF 
  Trace.WriteLine("We are writing to Trace."); 
}

 

 

Your own Logger: Extending the Logger Base Classes

If you want to create your own ILogger implementation, you can either create a new class that implements ILogger, or extend either LoggerBase or FormattableLoggerBase.

ILogger is not a complex interface, but there’s a lot of boilerplate code to implement. So in most cases, it’s probably more convenient to extend one of the base classes. Both require you to override just one method:

public class MyCustomLogger : LoggerBase 
{ 
  public override void Log(LogItem item) 
  { 
     //your implementation goes here 
  } 
}

 

 

Sample: Implementing a ColoredConsoleLogger

The class below is a custom console logger which writes colored output to the console, depending on the LogLevel of the logged item. The class derives from FormattableLoggerBase in order to allow you to plug in a custom message formatter.

public class ColoredConsoleLogger : FormattableLoggerBase
{
  public override void Log(LogItem item)
  {
    //set console color depending on log level
    switch (item.LogLevel)
    {
      case LogLevel.Info:
        Console.ForegroundColor = ConsoleColor.Green;
        break;
      case LogLevel.Warn:
        Console.ForegroundColor = ConsoleColor.Cyan;
        break;
      case LogLevel.Error:
        Console.ForegroundColor = ConsoleColor.Yellow;
        break;
      case LogLevel.Fatal:
        Console.ForegroundColor = ConsoleColor.Red;
        break;
      default:
        Console.ResetColor();
        break;
    }

    //delegate string formatting to base class 
    string text = FormatItem(item);

    //write the string to the console 
    Console.Out.WriteLine(text);
    Console.ResetColor();
  }
}

 

If you log through this ColoredConsoleLogger, a logged Info is displayed in green now:

image

 

Working with Available SLF Façade Libraries

SLF comes with a few façade libraries you can use to forward to a given logging frameworks. Currently, there is support for the following commonly known frameworks:

  • log4net
  • NLog
  • Enterprise Library
  • BitFactory Framework

…and if you would like to use SLF along with a framework that is not listed yet, writing your own façade is pretty easy!

We’ve provided samples and sample configurations for all these façades. Accordingly, you can just find the sample that suits you, and copy/paste the configuration code into your application.

Named Loggers

The “Named Loggers” feature allows you to define several loggers (programmatically or in code), and then access them through their names. You can even direct logging instructions to different logging frameworks based on logger names!

In order to get a named logger, just use the overload of LoggerService.GetLogger that takes a string parameter:

ILogger defaultLogger = LoggerService.GetLogger();
ILogger namedLogger   = LoggerService.GetLogger("LAB-DATA");

 

Note that the default logger is basically a named logger with a special name (an empty string). The snippets below all return the default logger:

ILogger defaultLogger = LoggerService.GetLogger(); 
ILogger defaultLogger = LoggerService.GetLogger("");
ILogger defaultLogger = LoggerService.GetLogger(LoggerService.DefaultLoggerName);

 

The downloadable SLF solution contains sample applications that show how to configure loggers with different names both programmatically or via configuration files.

The Hierarchical Lookup Process

When requesting a named logger, you don’t have to submit a name that exactly matches the name of a configured logger. Instead, named loggers are organized in a hierarchical manner. As soon as a named logger is being requested, SLF will just resolve the closest match and return you a matching logger instance:

imageIn the above sample, SLF was configured with a total of eight loggers. You can see, that the logger names form a hierarchy. With this setup in place, the closest match will be returned on every request.

Accordingly, LoggerService.GetLogger("Foo.AAA") would return a logger of type Foo (closest match). And LoggerService.GetLogger("Foo.XXX.AAA") would return a logger of type Foo.XXX (closest match again).

 

Here’s some more samples, matching the above hierarchy:

Requested Logger Name Returned Logger Type Name of Returned Logger Instance
     
"Foo" Type declared for "Foo" "Foo"
"Foo.XXX" Type declared for "Foo.XXX" "Foo.XXX"
"Foo.XXXXXX" Type declared for "Foo.XXX" "Foo.XXXXXX"
"Foo.X" Type declared for "Foo" "Foo.X"
"FooXXX" Type declared for "Foo" "FooXXX"
"Bar.XXX" Type declared for "Bar.XXX" "Bar.XXX"
"Bar.ZZZ" Type declared for "Bar" "Bar.ZZZ"
"XXX" Type declared for Default Logger "XXX"
"XXX.Foo" Type declared for Default Logger "XXX.Foo"
"TEST" Type declared for Default Logger "TEST"
"" Type declared for Default Logger ""
null Type declared for Default Logger ""

 

Fallback to Default  Logger

The lookup for a logger will always fall back to the default logger type if no matching named logger was found. This should not come as a surprise. After all, the default logger is just a logger with the name [String.Empty].

And don’t forget – LoggerService always returns a valid logger instance and never a null reference. If no unnamed logger was configured at all, you would just receive an instance of NullLogger.

 

Exceptions to Named Logger Resolving

Hierarchical lookups work out of the box, but they are not guaranteed if you customize LoggerService:

  • If you inject a single ILogger instance into LoggerService via LoggerService.SetLogger, all requests will of course return this specific logger instance, regardless of the requested logger name.
  • If you inject a custom resolver into LoggerService by setting the LoggerService.FactoryResolver property, you gain full control over the lookup and logger creation process. Accordingly, you can completely change this behavior.

Configuring Named Loggers in Code or Declaratively

The sample projects show how to set up named loggers in code, or via the application configuration file (app.config).

image

 

Filtering

Currently, SLF provides not built-in filtering mechanism, but leaves that to the underlying logging frameworks. The reason for this is that we just wanted to provide a very lean interface to get started with.

However, we’ve already opened a topic on CodePlex’ discussion board, so please express your opinion. If we feel there is a demand for built-in filtering, we will provide the functionality in a matter of days:

http://slf.codeplex.com/Thread/View.aspx?ThreadId=76857

 

Tutorial Part 2

The next part of this tutorial will discuss Resolvers, Factories, and creating your own logging façades. It will cover simple scenarios (you can plug-in factories with a single line of code) and more complex resolution scenarios for those among you who happen to deal with challenging logging situations. Stay tuned :)


Author: Categories: SLF Tags: ,
  1. December 19th, 2009 at 16:20 | #1

    Thank you for the article, it is very easy to follow and undestand.
    Any estimated date for part two of the tutorial?

  2. December 19th, 2009 at 16:43 | #2

    Alexandrul,

    Thanks for the feedback! I’ve already written most of it, I hope I can publish it next week.

    Happy coding :)

  3. sbx
    January 8th, 2010 at 15:56 | #3

    Hi,

    wonderful crafted piece of code thanks. We will use it in our next project. I had a little testrun today and found 2 things:

    a) It seems that its not possible to configure a composite logger in app.conf, right?

    b) I personally would prefer that the constructor of a logger has a overloaded variant where I can set the formatter directly (the abstract base class has this)

  4. January 19th, 2010 at 12:40 | #4

    any news about the second part? even a draft?

  5. Martin
    April 20th, 2010 at 17:17 | #5

    +1 for a second part!

  6. Tevya
    June 17th, 2010 at 20:08 | #6

    How does one configure SLF via a separate config file? E.g. If one has a web site web.config and one would like the log4net stuff to be kept in log4net.config instead.

  7. Giulliano
    August 26th, 2011 at 05:02 | #7

    Hello, Philipp!

    I enjoyed your tutorial. I’m thinking of integrating SQL with NHibernate. Do you have any hint or suggestion to pass me?

    Thanks in advance

  8. Giulliano
    August 26th, 2011 at 05:05 | #8

    Hello, Philipp!

    correction:
    I enjoyed your tutorial. I’m thinking of integrating the SLF and NHibernate. Do you have any hint or suggestion to pass me?

    Thanks in advance

  1. December 2nd, 2009 at 16:43 | #1
  2. December 2nd, 2009 at 18:14 | #2
  3. November 4th, 2013 at 08:40 | #3