Search code examples
c#log4netstructuremapstructuremap3

How can I wire StructureMap with 2 different objects (log4net loggers) of the same interface


I have a class named MyClass, which uses two different loggers named Logger1 and Logger2. I use log4net for logging, and want to use StructureMap for DI.

Without StructureMap, my class would look like this:

public class MyClass
{
    private static readonly ILog Logger1 = LogManager.GetLogger("Logger1"); // Loggers are configured in a config file
    private static readonly ILog Logger2 = LogManager.GetLogger("Logger2");

    public void DoSomething()
    {
        ...
        Logger1.Info("did something");
        ...
        Logger2.Info("need to log this elsewhere");
    }
 }

Introducing DI, with StructureMap (using v3.0.3), I would make the loggers instance members, and inject them into the constructor, like this: public class MyClass { private readonly ILog Logger1; private readonly ILog Logger2;

    myClass(ILog logger1, ILog logger2)
    {
        this.Logger1 = logger1;
        this.Logger2 = logger2;
    }

    public void DoSomething()
    {
        ...
        Logger1.Info("did something");
        ...
        Logger2.Info("need to log this elsewhere");
    }
 }

The thing is, I cannot get StructureMap to wire this up for me properly. I tried wiring the loggers like this:

For<ILog>.Use(()=> LogManager.GetLogger("Logger1")).Named("Logger1");
For<ILog>.Use(()=> LogManager.GetLogger("Logger2")).Named("Logger2");

Doing this Gets me empty (unconfigured) loggers). Replacing Use() with Add() gives my an exception due to not having a default instance registered for ILog.

Does anybody know how I can do this?


Solution

  • I ended up doing the following: I created two interfaces as per Rob's advice: ILogger1 and ILogger2. Since the both have the same API, as I need the same kind of functionality from them, they both inherit from the same interface - though not log4net.ILog as per Steven's advice:

    interface IMyLog
    {
        void Info(object message);
        void Info(string format, params object[] args);
    }
    
    interface ILogger1 : IMyLog { }
    interface ILogger2 : IMyLog { }
    

    Also, since the implementation of this API is the same for my needs, I have one concrete class MyLogger, implementing both ILogger1 and ILogger2. If I ever need the implementations to be different it will be easy for me to have explicit interface implementation, or separate classes. Only My Logger takes a dependency on log4net, as it uses it for its implementation:

    enum LoggerType { Logger1, Logger2 }
    
    internal class MyLogger : ILogger1, ILogger2
    {
        private readonly ILog _log;
    
        public MyLogger(LoggerType loggerName)
        {
            switch (loggerName)
            {
                case LoggerType.Logger1:
                    _log = LogManager.GetLogger("first-log");
                    break;
                case LoggerType.Logger2:
                    _log = LogManager.GetLogger("second-log");
                    break;
                default:
                    throw new ArgumentException("Invalid logger name", "loggerName");
            }
        }
    
        public void Info(object message)
        {
            _log.Info(message);
        }
    
        public void Info(string format, params object[] args)
        {
            _log.InfoFormat(format, args);
        }
    } 
    

    In order to register it with StructureMap, I used the following code in the registry:

    For<ILogger1>().Use<MyLogger>().Ctor<LoggerType>("loggerName").Is(LoggerType.Logger1).Singleton();   // I want only one logger per type
    For<ILogger2>().Use<MyLogger>().Ctor<LoggerType>("loggerName").Is(LoggerType.Logger2).Singleton();
    

    It all works wonderfully. So, thanks to Steven and Rob for their advice. I really learned something. I wish I could upvote an answer and a response more than once.

    So, to summarize, I:

    • Created a separate interface for each kind of logger (even saying it now sounds intuitive).
    • Created a base interface for the logger interfaces, because for my needs they have the same API (YMMV)
    • Created one concrete logger adapter that implements both interfaces because that suits my needs (YMMV again)
    • Share the implementation of the interfaces' API, for the above reasons
    • Registered each interface to create a concrete logger with a different type passed in the constructor
    • configured the concrete logger with log4net using a Factory Method to determine which logger to use