Introducing this.Log

One of my favorite creations over the past year has been this.Log(). It works everywhere including static methods and in razor views. Everything about how to create it and set it up is in this gist.

How it looks

public class SomeClass {
 
  public void SomeMethod() {
    this.Log().Info(() => "Here is a log message with params which can be in Razor Views as well: '{0}'".FormatWith(typeof(SomeClass).Name));

    this.Log().Debug("I don't have to be delayed execution or have parameters either");
  }

  public static void StaticMethod() {
    "SomeClass".Log().Error("This is crazy, right?!");
  }
 
}

Why It’s Awesome

  • It does no logging if you don’t have a logging engine set up.
  • It works everywhere in your code base (where you can write C#). This means in your razor views as well!
  • It uses deferred execution, which means you don’t have to mock it to use it with testing (your tests won’t fail on logging lines).
  • You can mock it easily and use that as a means of testing.
  • You have no references to your actual logging engine anywhere in your codebase, so swapping it out (or upgrading) becomes a localized event to one class where you provide the adapter.

Some Internals

This uses the awesome static logging gateway that JP Boodhoo showed me a long time ago at a developer bootcamp, except it takes the concept further. One thing that always bothered me about the static logging gateway is that it would construct an object EVERY time you called the logger if you were using anything but log4net or NLog. Internally it likely continued to reuse the same object, but at the codebase level it appeared as that was not so.

/// <summary>
/// Logger type initialization
/// </summary>
public static class Log
{
    private static Type _logType = typeof(NullLog);
    private static ILog _logger;
 
    /// <summary>
    /// Sets up logging to be with a certain type
    /// </summary>
    /// <typeparam name="T">The type of ILog for the application to use</typeparam>
    public static void InitializeWith<T>() where T : ILog, new()
    {
        _logType = typeof(T);
    }
 
    /// <summary>
    /// Sets up logging to be with a certain instance. The other method is preferred.
    /// </summary>
    /// <param name="loggerType">Type of the logger.</param>
    /// <remarks>This is mostly geared towards testing</remarks>
    public static void InitializeWith(ILog loggerType)
    {
        _logType = loggerType.GetType();
        _logger = loggerType;
    }
 
    /// <summary>
    /// Initializes a new instance of a logger for an object.
    /// This should be done only once per object name.
    /// </summary>
    /// <param name="objectName">Name of the object.</param>
    /// <returns>ILog instance for an object if log type has been intialized; otherwise null</returns>
    public static ILog GetLoggerFor(string objectName)
    {
        var logger = _logger;
 
        if (_logger == null)
        {
            logger = Activator.CreateInstance(_logType) as ILog;
            if (logger != null)
            {
                logger.InitializeFor(objectName);
            }
        }
 
        return logger;
    }
}

You see how when it calls InitializeFor, that’s when you get something like the following in the actual implemented method:

_logger = LogManager.GetLogger(loggerName);

So we take the idea a step further by implementing the following in the root namespace of our project:

/// <summary>
/// Extensions to help make logging awesome
/// </summary>
public static class LogExtensions
{
    /// <summary>
    /// Concurrent dictionary that ensures only one instance of a logger for a type.
    /// </summary>
    private static readonly Lazy<ConcurrentDictionary<string,ILog>> _dictionary = new Lazy<ConcurrentDictionary<string, ILog>>(()=>new ConcurrentDictionary<string, ILog>());
 
    /// <summary>
    /// Gets the logger for <see cref="T"/>.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="type">The type to get the logger for.</param>
    /// <returns>Instance of a logger for the object.</returns>
    public static ILog Log<T>(this T type)
    {
        string objectName = typeof(T).FullName;
        return Log(objectName);
   }
 
    /// <summary>
    /// Gets the logger for the specified object name.
    /// </summary>
    /// <param name="objectName">Either use the fully qualified object name or the short. If used with Log&lt;T&gt;() you must use the fully qualified object name"/></param>
    /// <returns>Instance of a logger for the object.</returns>
    public static ILog Log(this string objectName)
    {
        return _dictionary.Value.GetOrAdd(objectName, Infrastructure.Logging.Log.GetLoggerFor);
    }
}

You can see I’m using a concurrent dictionary which really speeds up the operation of going and getting a logger. I get the initial performance hit the first time I add the object, but from there it’s really fast. I do take a hit with a reflection call every time, but this is acceptable for me since I’ve been doing that with most logging engines for awhile.

Conclusion

If you are interested in the details, see this gist.

Extensions are awesome if used sparingly. Is this.Log perfect? Probably not, but it does have a lot of benefits in use. Feel free to take my work and make it better. Find a way to get me away from the reflection call every time. I’ve been using it for almost a year now and have improved it a little here and there.

If there is enough interest, I can create a NuGet package with this as well.


Posted 12-15-2012 9:29 AM by Rob Reynolds
Filed under: ,

[Advertisement]

Comments

Jega wrote re: Introducing this.Log
on 12-15-2012 2:09 PM

Hi Rob,

This is cool. It would be great if you could create a Nuget package for it.

-Jega

Jega wrote re: Introducing this.Log
on 12-15-2012 2:10 PM

Hi Rob

This is so cool. Great if you could create a Nuget package.

Thanks

Jega

J wrote re: Introducing this.Log
on 12-15-2012 11:15 PM

Sticking Lazy around the concurrent dictionary seems unnecessary (construction of the dictionary takes very little time tested with 100000 iterations and a stopwatch).

Conversely, the Log class could be cleaner if you used Lazy(T)

Have you considered InitializeWith(Func(T) teaFactory)* instead of storing the type and using Activator.CreateInstance()?

* parentheses instead of angle brackets, because I'm not sure what gets swallowed.

Stuart wrote re: Introducing this.Log
on 12-17-2012 3:38 AM

This looks pretty good. Some combination of this and the way that Caliburn.Micro manages logging (see caliburnmicro.codeplex.com/.../1c05274733b7) would be pretty awesome.

Mark Rendle wrote re: Introducing this.Log
on 12-17-2012 5:00 AM

In C# 5, adding the [CallerMemberName] etc attributes to the extension methods could improve this still further.

Stuart wrote re: Introducing this.Log
on 12-17-2012 9:09 AM

In fact, going off my earlier comment, I think you could replace your public static class Log with:

public static class LogManager

   {

       static readonly ILog NullLogInstance = new NullLogger();

       public static Func<Type, ILog> GetLog = type => NullLogInstance;

   }

(From Caliburn.Micro), then in your extension method class you use LogManager.GetLog(typeof (T));

Matt Davey wrote re: Introducing this.Log
on 12-17-2012 11:23 AM

@Mark Rendle I had the same thought but unfortunately you can't get the name of the type with caller info attributes, just the method/file/line.

Matt wrote re: Introducing this.Log
on 12-17-2012 3:23 PM

It would be great to have a nuget package

angol wrote re: Introducing this.Log
on 12-17-2012 3:45 PM

vote for pedro, vote for nuget:)

Rob Reynolds wrote re: Introducing this.Log
on 12-20-2012 12:29 AM
Anonymous wrote re: Introducing this.Log
on 01-10-2013 2:15 AM

" Feel free to take my work and make it better."

Meaning, go ahead and use this solution freely in any situation possible?

Rohan Cragg wrote re: Introducing this.Log
on 01-16-2013 6:24 AM

Awesome indeed!

Had you considered supporting an overload of .Error to pass in an Exception as a second parameter?

I ask because NLog supports rendering of Exception info:

nlog-project.org/.../How_to_properly_log_exceptions%3F

Rob Reynolds wrote re: Introducing this.Log
on 01-30-2013 2:25 PM

@Rohan: Both log4Net and NLog support the rendering of exceptions. I take pull requests. :D

Patric wrote re: Introducing this.Log
on 03-05-2013 11:52 AM

Hi,

is it possible that you can make the nuget-package signed with a string-name key?

// Patric

About The CodeBetter.Com Blog Network
CodeBetter.Com FAQ

Our Mission

Advertisers should contact Brendan

Subscribe
Google Reader or Homepage

del.icio.us CodeBetter.com Latest Items
Add to My Yahoo!
Subscribe with Bloglines
Subscribe in NewsGator Online
Subscribe with myFeedster
Add to My AOL
Furl CodeBetter.com Latest Items
Subscribe in Rojo

Member Projects
DimeCasts.Net - Derik Whittaker

Friends of Devlicio.us
Red-Gate Tools For SQL and .NET

NDepend

SlickEdit
 
SmartInspect .NET Logging
NGEDIT: ViEmu and Codekana
LiteAccounting.Com
DevExpress
Fixx
NHibernate Profiler
Unfuddle
Balsamiq Mockups
Scrumy
JetBrains - ReSharper
Umbraco
NServiceBus
RavenDb
Web Sequence Diagrams
Ducksboard<-- NEW Friend!

 



Site Copyright © 2007 CodeBetter.Com
Content Copyright Individual Bloggers

 

Community Server (Commercial Edition)