May 242012
 

NOTE: I have since solved this problem with a Ninject module that makes things much simpler. Check out this post for details.

This post describes a pattern I came up with for providing logging in an application is a very elegant way, with consumers of loggers able to use loggers that are injected via property injection. But, at test time if the logger is null, a NullReferenceException is prevented so that tests can be executed without having to provide a mocked logging interface.

I’m a big fan of the dependency injection / inversion of control pattern. Lately, I’ve been using Ninject as my framework-of-choice, but the technique I talk about in this post could be applied to any feature-complete DI framework.

Generally, it is a best practice to use constructor injection for non-optional dependencies, such as if you have a WidgetProcessor that needs an IWidgetRepository to do its work. However, for optional dependencies, I prefer to use property injection, as it makes the constructors a little cleaner and easier to use from unit tests. I view logging as an optional dependency, since you want logging to occur in a production system, but you generally don’t want logging to occur when you are executing unit tests.

So, for logging, ideally my business logic class would look something like this:

public class BusinessLogicClass
{
    [Inject]
    public ILogger Log { get; set; }
 
    public void DoSomething()
    {
        Log.Info("Something is being done...");
        // TODO: Do something
    }
}

The [Inject] attribute above the Log property allows Ninject to give the class a logger at runtime. But you don’t need a logger to construct the class. However, the astute reader will notice a problem: the DoSomething() method will crash with a null reference exception if the Log property is not injected. So any tests that execute DoSomething() will fail. You could get around this by having each test manually inject a mocked logger, or having the DoSomething() method check for null before calling the logger, or by using Ninject in all of the tests to make sure a logger is actually present. But none of these solutions are very elegant in my opinion.

Instead, the solution I came up with is to make ILogger an empty interface, and provide the logging methods on it via extension methods. The extension methods can prevent a null reference exception, without the consumers of ILogger having to do anything special.

So we end up with this:

public interface ILogger
{
}
 
public interface ILoggerInternal
{
    void Info(string message);
}
 
public static class LoggerExtensionMethods
{
    public static void Info(this ILogger logger, string message)
    {
        if (logger == null)
            return;
        ((ILoggerInternal)logger).Info(message);
    }
}

Now if the DoSomething() method calls ILogger.Info() on a null instance, the Info() extension method will prevent a NullReferenceException and everything will work smoothly at test time. Note that I only show the Info method, but all of the other standard logging methods (Error, Warning, Fatal, etc.) with all of their overloads would be included in a full implementation.

With this pattern in place, you just need a class that implements both ILogger and ILoggerInternal, and then you need to inject that class via Ninject when anything wants an ILogger. I have included some code that demonstrates this with log4net but the same pattern could apply to any logging implementation.

Here is the zipped up code. Note that in order to save on download size, I did not include the Nuget packages for log4net, moq, or Ninject. So you will have to pull those down to get the code to build.

NinjectOptionalLoggingPattern.zip

 Leave a Reply

(required)

(required)

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>