In a recent post I described some initial impressions of the MS Live Labs Simple Logging Framework (SLF). One of the things I really like about SLF is the use of lambas as a way to defer execution of potentially slow or unchecked message formatting code. I also railed on the idea of creating a completely new effort when log4net is an active open-source project.
So, here I am putting my money where my mouth is: I've created some extension methods for instances of ILog that accept lambdas - now if you use log4net in a C# 3.0+ environment, you can defer message formatting as easily as you can in SLF. The code is quite simple:
using log4net;
namespace Log4NetExtensions
{
public static class Log4NetExtensionMethods
{
public static void Debug( this ILog log, Func<string> formattingCallback )
{
if( log.IsDebugEnabled )
{
log.Debug( formattingCallback() );
}
}
public static void Info( this ILog log, Func<string> formattingCallback )
{
if( log.IsInfoEnabled )
{
log.Info( formattingCallback() );
}
}
public static void Warn( this ILog log, Func<string> formattingCallback )
{
if( log.IsWarnEnabled )
{
log.Warn( formattingCallback() );
}
}
public static void Error( this ILog log, Func<string> formattingCallback )
{
if( log.IsErrorEnabled )
{
log.Error( formattingCallback() );
}
}
public static void Fatal( this ILog log, Func<string> formattingCallback )
{
if( log.IsFatalEnabled )
{
log.Fatal( formattingCallback() );
}
}
}
}
Each method of the ILog interface gets its own override in the form of an extension method. The method accepts a Func<string> that allows you to capture log message formatting in a lamba expression, and hence defer its execution until absolutely necessary (or avoid it altogether). Here are some quick and dirty unit tests to demonstrate the basic use and functionality:
using NUnit.Framework;
namespace Log4NetExtensions.Tests
{
[TestFixture]
public class Log4NetExtensionTests
{
log4net.ILog Log;
log4net.Appender.MemoryAppender appender;
[TestFixtureSetUp]
public void FixtureSetUp()
{
Log = log4net.LogManager.GetLogger( System.Reflection.MethodBase.GetCurrentMethod().DeclaringType );
appender = new log4net.Appender.MemoryAppender
{
Name = "Memory Appender",
Threshold = log4net.Core.Level.Debug
};
log4net.Config.BasicConfigurator.Configure( appender );
}
[SetUp]
public void TestSetUp()
{
appender.Clear();
log4net.Core.LoggingEvent[] events = appender.GetEvents();
Assert.That( 0 == events.Length, "failed to clear appender of log events" );
Log.Logger.Repository.Threshold = log4net.Core.Level.Debug;
}
[Test]
public void LogViaLambdaTest()
{
Log.Debug( () => "Hello World!" );
log4net.Core.LoggingEvent[] events = appender.GetEvents();
Assert.That( 1 == events.Length, "failed to log via lamba" );
Assert.That(
StringComparer.CurrentCulture.Equals(
"Hello World!",
events[ 0 ].RenderedMessage
),
"rendered message does not match (via lambda)"
);
}
[Test]
public void LogWithLocalVariableReference()
{
string value = "World!";
Log.Debug( () => "Hello " + value );
log4net.Core.LoggingEvent[] events = appender.GetEvents();
Assert.That( 1 == events.Length, "failed to log with local variable reference" );
Assert.That(
StringComparer.CurrentCulture.Equals(
"Hello World!",
events[ 0 ].RenderedMessage
),
"rendered message does not match (local variable reference)"
);
}
[Test]
public void LambdaIsNotEvaluatedAtInactiveLogLevel()
{
Log.Logger.Repository.Threshold = log4net.Core.Level.Error;
bool evaluated = false;
Log.Debug( () =>
{
evaluated = true;
return "Hello World!";
}
);
Assert.That( ! evaluated, "lamba was evaluated at inactive log level" );
Log.Error( () =>
{
evaluated = true;
return "Hello World!";
}
);
Assert.That( evaluated, "lamba was not evaluated at active log level" );
}
}
}
Play with it, see if you like it. I will to. Any suggestions or comments are encouraged. If there is enough interest I'll see about submitting the extensions to the apache project.