StructureMap/Ninject + log4net

One fine day, my task was to refactor my classes so they did not have a dependency on log4net.

The first thing I did was to create an ILogger interface in my core library, and an Log4NetLogger class in my infrastructure library. Followed by adding a line to my IoC container bootstrapper class. Finally, I added an ILogger constructor argument to each of my classes that needed logging. Finished in no time, or so I thought.

 

The Problem

I quickly realized that the contents of my log files had lost one important piece of information that nearly rendered them useless.

2009-04-07 07:10:15,810 [1] DEBUG aspZone.LoggingExample.Log4NetLogger – Called Foo.DoSomething
2009-04-07 07:10:15,843 [1] DEBUG aspZone.LoggingExample.Log4NetLogger – Called Bar.DoSomething

In my Log4NetLogger constructor, I had called LogManager.GetLogger as per common practice:

   1: public Log4NetLogger()
   2: {
   3:     this.logger = log4net.LogManager.GetLogger(
   4:         System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
   5: }

As a result, there was no way to filter my log entries by which logger they came from. Every single log entry looked like it came from Log4NetLogger. This had to change.

The Solution

After a bunch of searching and a few tweets, I found what I think is a very neat solution.

I changed my ILogger interface to ILogger<T>:

   1: using System;
   2:  
   3: namespace aspZone.LoggingExample
   4: {
   5:     public interface ILogger<T>
   6:     {
   7:         void Debug(object message);
   8:         // ...
   9:     }
  10: }

And I modified my Log4NetLogger like this:

   1: using System;
   2: using log4net;
   3:  
   4: namespace aspZone.LoggingExample
   5: {
   6:     public class Log4NetLogger<T> : ILogger<T>
   7:     {
   8:         private readonly ILog logger;
   9:  
  10:         public Log4NetLogger()
  11:         {
  12:             this.logger = LogManager.GetLogger(typeof(T));
  13:         }
  14:  
  15:         // ...
  16:     }
  17: }

 

And using generic type inference, a syntax I was previously unaware of, to register the types in StructureMap:

   1: using StructureMap.Configuration.DSL;
   2:  
   3: namespace aspZone.LoggingExample
   4: {
   5:     public class ExampleRegistry : Registry
   6:     {
   7:         public ExampleRegistry()
   8:         {
   9:             ForRequestedType(typeof(ILogger<>))
  10:                 .TheDefaultIsConcreteType(typeof(Log4NetLogger<>));
  11:             // ...
  12:         }
  13:     }
  14: }

The syntax for Ninject is very similar:

   1: Bind(typeof(ILogger<>)).To(typeof(Log4NetLogger<>));

Conclusion

My log entries are filterable like they were before.

2009-04-07 07:10:15,810 [1] DEBUG aspZone.LoggingExample.Foo – Called Foo.DoSomething
2009-04-07 07:10:15,843 [1] DEBUG aspZone.LoggingExample.Bar – Called Bar.DoSomething

And now I have much better separation of concerns.

Comments

#1 Luca Milan on Wednesday, April 08 2009 at 5:20 AM

Good! But why component lifestyle is per-request and not singleton ?