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.