10

I have the following interface:

    public interface ILogger
    {
        void Debug(string message, params object[] values);
        void Info(string message, params object[] values);
        void Warn(string message, params object[] values);
        void Error(string message, params object[] values);
        void Fatal(string message, params object[] values);
    }

and the following implementation:

    public class Log4netLogger : ILogger
    {
        private ILog _log;

        public Log4netLogger(Type type)
        {
            _log = LogManager.GetLogger(type);
        }

        public void Debug(string message, params object[] values)
        {
            _log.DebugFormat(message, values);
        }

        // other logging methods here...

    }

My idea was to use structuremap to instantiate the Log4netLogger class with using the Type of the class that did the logging. However, I can't for the life of me figure out how to pass the type of the calling class to structuremap so that it can be passed to the constructor of the logging implementation. Any advice on how to do that (or a better way) would be most appreciated.

Josh Gallagher
  • 5,211
  • 2
  • 33
  • 60
Chris
  • 27,596
  • 25
  • 124
  • 225

3 Answers3

26

We use a similar ILogger wrapper around log4net and typically use constructor injection. We use an interceptor as a factory method responsible for creating the Logger. Here is our typical registry for logging setup.

    public class CommonsRegistry : Registry
    {
        public CommonsRegistry()
        {
            For<ILogger>()
                .AlwaysUnique()
                .TheDefault.Is.ConstructedBy(s =>
                {
                    if (s.ParentType == null)
                        return new Log4NetLogger(s.BuildStack.Current.ConcreteType);
                    
                    return new Log4NetLogger(s.ParentType);
                });

            var applicationPath = Path.GetDirectoryName(Assembly.GetAssembly(GetType()).Location);
            var configFile = new FileInfo(Path.Combine(applicationPath, "log4net.config"));
            XmlConfigurator.ConfigureAndWatch(configFile);
        }
    }

The parent type null check is necessary when there are dependencies on concrete types.

The rest is optional log4net setup stuff.

One thing I do like about this setup is the ability to use a null loggers for unit testing.

Josh Gallagher
  • 5,211
  • 2
  • 33
  • 60
KevM
  • 2,496
  • 1
  • 22
  • 25
  • Interesting, I'll have to try this. Thanks! – Chris Dec 28 '09 at 17:54
  • This is indeed interesting. I also saw your comment on my answer. Would this work if the ctor dependency was anything outside of the calling class's type? Would love to see a blog post on context-specific ctor dependencies, as this has to be the #1 most asked question about using a DI framework. – Phil Sandler Dec 28 '09 at 22:30
  • Also, I'm struggling with whether this solution makes the dependency less explicit. If construction really requires a factory, is it a good idea to "hide" the factory in configuration code, instead of making the dependency on an abstract factory explicit? (I mean this as a theoretical question, not necessarily as the best solution for Chris or for logging per se). – Phil Sandler Dec 28 '09 at 22:34
  • Not sure I understand your ctor question. When you put a dependency into your constructor StructureMap will make sure it gets injected into the instances of this class when they are created. This is true for each type down the graph of each type's dependencies. Jeremy just posted about a new feature nested containers which give you control over contextual dependencies: http://codebetter.com/blogs/jeremy.miller/archive/2010/01/06/how-dovetail-uses-structuremap-with-nhibernate.aspx – KevM Jan 06 '10 at 21:21
  • A factory of this nature is really configuration so it feels right having it live in the registry where we put most of the our application's configuration. If you are worried create a static factory method. When an IoC container is in play it feels silly having overly simple factory classes describing how to create types. as that is what IoC is for. – KevM Jan 06 '10 at 21:25
1

If the type parameter is context-specific, I don't think this is going to work as shown. If you need to pass something context specific in the constructor, you are likely going to have to create a factory interface and implementation that returns an instance of the ILogger:

    public interface ILoggerFactory
    {
        ILogger Create(Type type);   
    }
    
    public class LoggerFactory : ILoggerFactory
    {
        public ILogger Create(Type type)
        {
            return new Log4netLogger(type);
        }
    }

It might be possible to bootstrap StructureMap to supply the instance you want based on the type, but that assumes a limited number of types that you know in advance.

Josh Gallagher
  • 5,211
  • 2
  • 33
  • 60
Phil Sandler
  • 27,544
  • 21
  • 86
  • 147
  • 1
    The need for factories when using structuremap can usually be eliminated by using an "ConstructedBy" with a lambda as the factory method. http://structuremap.sourceforge.net/InstanceExpression.htm#section13 – KevM Dec 28 '09 at 20:53
1

I really need to get out of the habit of answering my own question, but for those who run across it, here's the answer.

return ObjectFactory.With(type).GetInstance<T>();

I actually have a wrapper to structuremap (to avoid exposing the structuremap dependency to my app) that looks like the following:

public static class ServiceManager
{
    public static T Get<T>()
    {
        return ObjectFactory.GetInstance<T>();
    }

    public static T Get<T>(Type type)
    {
        return ObjectFactory.With(type).GetInstance<T>();
    }
}

Any time in the code I need a logger, I call the following:

ServiceManager.Get<ILogger>(GetType()).Info("Logging page view...");
Chris
  • 27,596
  • 25
  • 124
  • 225
  • 1
    I actually don't think there is anything wrong with considering the logger a global service (as it is generally cross-cutting), but this does create a static dependency on your ServiceManager. I'm not saying your solution is wrong (far from it), but it sounds a little bit like it may tend toward the global service locator "anti-pattern". Essentially your dependency on ILogger (or ILoggerFactory) is (or may be) no longer explicit. – Phil Sandler Dec 21 '09 at 23:01
  • Interesting point Phil. I'm not sure what the alternative would be though (architectural recommendations on IoC are sparse, indeed). In my case, the class is an HttpHandler which needs access to the ILogger it is requesting from the ServiceManager. How else would I implement? – Chris Dec 22 '09 at 00:04
  • I absolutely agree on architecture recommendations being sparse, and your question IMHO is *the* most common hurdle people come across when they are getting started with a DI framework. Creating an abstract factory is the best answer I have come up with, and I often implement the factory using StructureMap (ObjectFactory.GetInstance) so that dependencies further down the chain can still be container-managed. This may not be *perfect*, but at least dependencies are kept explicit. – Phil Sandler Dec 22 '09 at 14:36
  • Hmmm... since the code is in an HttpHandler, I can't really use a factory for it. Maybe I should move the code to a different class and use the IoC container to load that class with the ILogger as an explicit dependency in the constructor. Would that be more appropriate? – Chris Dec 22 '09 at 15:55
  • I don't know that much about HttpHandlers. I'm guessing this class isn't/can't be manged by the container? If that's the case, and you're not looking at a deep dependency chain beyond this class, then you should probably ignore my comments and not belabor the design. :) – Phil Sandler Dec 22 '09 at 16:24
  • Just checking in to make sure you know about AutoWiring in structure map. http://codebetter.com/blogs/jeremy.miller/archive/2009/01/07/autowiring-in-structuremap-2-5.aspx Service location while sometimes necessary is best avoided when possible. Let the container do your work for you via auto wiring and injection. Ctor > Setter > Service Location. – KevM Dec 28 '09 at 20:47
  • @Kevin, as I stated previously to Phil, the issue is that the class in question is an HttpHandler which is instantiated by ASP.NET directly, I have no control over the process, so I cannot inject structuremap into the pipeline to let it do the autowiring. However, what I did end up doing is moving the main logic to a class that accepts ILogger as a constructor arg and then calling ObjectFactory from the HttpHandler, allowing it to fill in the ILogger argument automatically. Best approach I could think of. – Chris Dec 29 '09 at 00:45
  • 1
    @Chris: In order to DI into an HttpHandler you can use a custom PageHandlerFactory. See http://aspnetresources.com/articles/ioc_and_di_with_web_forms.aspx for more info – PHeiberg Mar 02 '10 at 21:32