mpLogo.png

Project Description

Existing logging libraries are either too feature-light or too complex. That means you might end up spending as much time debugging your logging code as you do your application code. Or you may not even know that your “log” to database is even working until it’s too late.

I wanted a simple and small logging library. I wanted to be able to configure the entire library via code or configuration file. I wanted sensible defaults. I wanted to take advantage of .NET 3.5 functionality. I wanted to explore using .NET delegates instead of interfaces as an extensibility mechanism. But above all I wanted simple.

So here is a logging library that’s full-featured enough, yet simple enough, for you to use as you like.

More Documentation

Quick Start

using LiveLabs.Logging;

public class MyDAO
{
   private static readonly Logger sLogger = new Logger(typeof(MyDAO));
}


The defaults out of the box are as follows
  • Info LogLevel
  • All logs go to STDERR (Console.Error)

These can be changed using the LiveLabs.Logging.Settings class. Here is some code that changes the logging level for MyDAO listed above.

using LiveLabs.Logging;

Settings.Default.RegisterLogLevelFor<MyDAO>(LogLevel.Debug);

// change for ALL loggers
Settings.Default.DefaultLogLevel = LogLevel.Error;


While STDERR is a good default for systems that have little debug output, you'll typically want to change the default logging location to a file. You can change it for all Loggers or for one specific Logger. To change for all Logger objects, you do this...

using LiveLabs.Logging;
using LiveLabs.Logging.Logs;

Settings.Default.DefaultLog = new FileLog("my_log_file.log");


NOTE FileLog in the above case is NOT threadsafe. To make it threadsafe, use the following.

Settings.Default.DefaultLog = new LockingLog(new FileLog("my_log_file.log"));


For one specific Logger, you use a Logger constructor overload...

using LiveLabs.Logging;
using LiveLabs.Logging.Logs;

Logger myLogger = new Logger(typeof(MyDao), new LockingLog(new FileLog("my_special_file.log")));


Of course, no logging library is complete without a file log that rotates on some schedule. In my years, I've seen logging libraries that give infinite flexibility in when a file should rollover. This library does not. You have 2 choices - hourly or daily. Personally in my 10+ years of developing, I've never used anything more than hourly or daily. Here are some examples on creating a TimedRollingLog.

using LiveLabs.Logging;
using LiveLabs.Logging.Logs;

// all defaults
// Hourly using UTC Time to control roll schedule
TimedRollingLog log = new TimedRollingLog("my_rolling_file.log");

// Daily using UTC Time to control roll schedule
TimedRollingLog log = new TimedRollingLog("my_rolling_file.log", TimedRollingSchedule.Daily);

// Daily using Local Time to control roll schedule
TimedRollingLog log = new TimedRollingLog("my_rolling_file.log", TimedRollingSchedule.Daily, TimedRollingTimeBase.Local);


When writing this library, .NET 3.5 was specifically targeted to be able to take advantage of lambdas. Why lambdas you may ask? Safety and performance. You can see some justification at http://justinrudd.wordpress.com/2008/03/03/logging-in-net-part-3/. By lessening the surface area of inputs, the library can limit what it is responsible for checking. So how do lambdas get used?

Logger logger = new Logger(typeof(MyType));

// set the level to Error to ensure no output
logger.Level = LogLevel.Error;

// 1
logger.Inform("The standard string method");

// 2
logger.Inform(string.Format("The {0} string method", "formatted"));

// 3
logger.Inform(() => "The " + "concatenated" + " string method");


In the above 3 examples, 1 will generate a string object because the compiler does it on your behalf. But it doesn't get sent to the log and is reasonably cheap. 2 will always format a string, but in this case won't send it to the log. 3 will not generate anything (other than the compiler strings done on your behalf) because the lambda is only called if the Logger should log at that method level (Inform in these examples). So you can write arbitrarily complex string output without fear of messing up the format string or with ugly conditional code like

if(logger.IsInfoEnabled)
{
   logger.Info(string.Format("The {0} string method", "formatted"));
}


But one of the most requested features was to have this facility. So with 1.0, you can do this.

if(logger.WillLogAt(LogLevel.Error))
{
   // do whatever
}


Is there overhead in a lambda? Absolutely. But the .NET compiler is very good at optimizing them and not creating more than are really needed. For example, sample 3 above will only do one object allocation for the entire lifetime of your AppDomain because it isn't making use of anything outside of the lambda.

The final area to talk about in this quick start is log message formats. One thing about this library is to not have an explosion of interfaces for simple things. Generating a string to write to a Log is one such area. The Logger simply needs a method to call and a string returned. The Logger doesn't care where that method comes from or who implements it. To facilitate this, delegates are used. Specifically the following 2 delegates

public delegate string MessageFormatter(string msg, LoggerName name, LogLevel level);

public delegate string ExceptionFormatter(Exception ex, LoggerName name, LogLevel level);


You are free to generate whatever you want in your implementation. Default implementations are provided that generate the following output -

// MessageFormatter default
2008-05-13T17:10:22.12345Z|   |Current Thread Name|LoggerName|Your Message Here

//ExceptionFormatter
2008-05-13T17:10:22.12345Z|***|Current Thread Name|LoggerName|Your Exception Message
Your Exception Stack Trace


As with all things in this library, you can change the default or on a per logger basis.

using LiveLabs.Logging;

// always display QUACK QUACK
DefaultFormatters.Message = (msg, type, level) => "QUACK QUACK!";

// except for MyDAO...
Settings.Default.RegisterMessageFormatterFor<MyDAO>((msg, name, level) => msg);

// or even set it after creation for very special cases...
Logger logger = new Logger(typeof(MyDAO));
logger.MessageFormatter = (msg, name, level) => "Special Instance: " + msg;


For more details click here.

Last edited Feb 13, 2009 at 12:10 AM by justrudd, version 10