Search code examples
c#log4netsimple-injector

Simple Injector and a Logging Abstraction Using Log4net


In order to try and get a nice logging abstraction with Log4net, I took the abstraction from this SO post and the adapter from this SO post and tried to get them working together.

All that was really left to do was configure the container and that is the part which I have not succeeded in doing.

The config which I have tried is

public static class InfrastructureRegistry
{
    public static void RegisterLoggingServices(this Container container)
    {
        container.RegisterConditional(typeof(ILog), c => LogManager.GetLogger(
            c.Consumer.ImplementationType).GetType(), 
            Lifestyle.Scoped, c => true);
        container.RegisterPerWebRequest<ILogger, Log4netAdapter>();
    }
}

As you can see from the code, I would like a specific log4net logger which takes its Type from the class into which it is injected. Whilst most logging would be done in a catch-all, I want some logging to happen in lower layers e.g. when a form validation fails.

The ActivationException which I get with that configuration is :

The constructor of type LogImpl contains the parameter with name 'logger' and type ILogger that is not registered. Please ensure ILogger is registered, or change the constructor of LogImpl.

Not quite sure where to go from here, so any help would be appreciated.

Edit

Sorry, I should point out that I am trying to write it such that I only have to write this config once. The following factory function works, but I don't want to have to manually add more config every time I want to inject a logger:

container.RegisterPerWebRequest<ILog>(() => LogManager.GetLogger(typeof(LoginController)));

Solution

  • The example adapter you point at assumes a single logger for every component in the application, while what you wish is to have a specific logger that 'knows' about its consumer, so it can relate the log messages to the originating class.

    Although this seems to be a very common practice when working with tools like log4net and NLog, in my experience, this requirement often comes from the fact that logging is done at too many places in the code. Please read this stackoverflow q/a for more information.

    That said, if you want to register the logger conditionally, you will have to change the adapter to a generic class; that way you can make the registration conditional:

    public class Log4netAdapter<T> : ILogger
    {
        private static readonly log4net.ILog logger = LogManager.GetLogger(typeof(T));
    
        public void Log(LogEntry entry)
        {
            if(entry.LoggingEventType == LoggingEventType.Information)
                logger.Info(entry.Message, entry.Exception);
            else if(entry.LoggingEventType == LoggingEventType.Warning)
                logger.Warn(entry.Message, entry.Exception);
            else if(entry.LoggingEventType == LoggingEventType.Error)
                logger.Error(entry.Message, entry.Exception);
            else
                logger.Fatal(entry.Message, entry.Exception);
        }
    }
    

    With this generic class, you can do the following conditional/contextual registration:

    container.RegisterConditional(
        typeof(ILogger),
        c => typeof(Log4netAdapter<>).MakeGenericType(c.Consumer.ImplementationType),
        Lifestyle.Singleton,
        c => true);