25
votes

I use Ninject as a DI Container in my application. In order to loosely couple to my logging library, I use an interface like this:

public interface ILogger
    {
        void Debug(string message);
        void Debug(string message, Exception exception);
        void Debug(Exception exception);

        void Info(string message);
        ...you get the idea

And my implementation looks like this

public class Log4NetLogger : ILogger
    {
        private ILog _log;

        public Log4NetLogger(ILog log)
        {
            _log = log;
        }

        public void Debug(string message)
        {
            _log.Debug(message);
        }
        ... etc etc

A sample class with a logging dependency

public partial class HomeController
    {
        private ILogger _logger;

        public HomeController(ILogger logger)
        {
            _logger = logger;
        }

When instantiating an instance of Log4Net, you should give it the name of the class for which it will be logging. This is proving to be a challenge with Ninject.

The goal is that when instantiating HomeController, Ninject should instantiate ILog with a "name" of "HomeController"

Here is what I have for config

public class LoggingModule : NinjectModule
    {
        public override void Load()
        {
            Bind<ILog>().ToMethod(x => LogManager.GetLogger(GetParentTypeName(x)))
                .InSingletonScope();

            Bind<ILogger>().To<Log4NetLogger>()
                .InSingletonScope();
        }

        private string GetParentTypeName(IContext context)
        {
            return context.Request.ParentContext.Request.ParentContext.Request.Service.FullName;
        }
    }

However the "Name" that is being passed to ILog is not what I'm expecting. I can't figure out any rhyme or reason either, sometimes it's right, most of the time it's not. The Names that I'm seeing are names of OTHER classes which also have dependencies on the ILogger.

7
When it isn't right, what does it look like? Can you mock up a class relationship so you can give some fake names and at least explain the relationship between the names you are seeing? Or just copy some of your code? - Merlyn Morgan-Graham
The names that I'm seeing are names of other classes which have a dependency on ILogger. For example, in my HomeController, It's getting a logger with a name of SomethingRepository. - Brook
Acutally, I think you just made me solve it. the ILog/ILogger aren't scoped right. It's newing them for one instance, then reusing it. They should be scoped Transient. - Brook

7 Answers

28
votes

I personally have no interest in abstracting away my logger, so my implementation modules reference log4net.dll directly and my constructors request an ILog as desired.

To achieve this, a one line registration using Ninject v3 looks like this at the end of my static void RegisterServices( IKernel kernel ):

        kernel.Bind<ILog>().ToMethod( context=> 
            LogManager.GetLogger( context.Request.Target.Member.ReflectedType ) );
        kernel.Get<LogCanary>();
    }

    class LogCanary
    {
        public LogCanary(ILog log)
        {
            log.Debug( "Debug Logging Canary message" );
            log.Info( "Logging Canary message" );
        }
    }

For ease of diagnosing logging issues, I stick the following at the start to get a non-DI driven message too:

public static class NinjectWebCommon
{
    public static void Start()
    {
        LogManager.GetLogger( typeof( NinjectWebCommon ) ).Info( "Start" );

Which yields the following on starting of the app:

<datetime> INFO  MeApp.App_Start.NinjectWebCommon           - Start
<datetime> DEBUG MeApp.App_Start.NinjectWebCommon+LogCanary - Debug Logging Canary message
<datetime> INFO  MeApp.App_Start.NinjectWebCommon+LogCanary - Logging Canary message
18
votes

The Ninject.Extension.Logging extension already provides all you are implementing yourself. Including support for log4net, NLog and NLog2.

https://github.com/ninject/ninject.extensions.logging


Also you want to use the following as logger type:

context.Request.ParentRequest.ParentRequest.Target.Member.DeclaringType

Otherwise you will get the logger for the service type instead of the implementation type.

10
votes

The Scope of ILog and ILogger needs to be Transient, otherwise it will just reuse the first logger that it creates. Thanks to @Meryln Morgan-Graham for helping me find that.

8
votes
Bind<ILog>().ToMethod(x => LogManager.GetLogger(GetParentTypeName(x)))
            .InSingletonScope();

You are currently binding in Singleton scope, so only one logger is created which will use the name of the first one created. Instead use InTransientScope()

4
votes

maybe my answer is late but I'm using this format:

private static void RegisterServices(IKernel kernel)
    {
        kernel.Bind<ILog>()
            .ToMethod(c => LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType))
            .InSingletonScope();
    }
4
votes

For all of you that are still looking for the correct answer, the correct implementation is :

public class LoggingModule : NinjectModule
{
    public override void Load()
    {
        Bind<ILog>().ToMethod(x => LogManager.GetLogger(x.Request.Target.Member.DeclaringType));

        Bind<ILogger>().To<Log4NetLogger>()
            .InSingletonScope();
    }
}

Emphasis on:

x.Request.Target.Member.DeclaringType
0
votes

I do like the idea of wrapping the Log4Net in my own interfaces. I don't want to be dependent on Ninjects implementation, because to me that just means I take a dependency on Ninject throughout my application and I thought that was the exact opposite of what dependency injection is for. Decouple from third party services. So I took the original posters code but I changed the following code to make it work.

    private string GetParentTypeName(IContext context)
    {
        var res = context.Request.ParentRequest.ParentRequest.Service.FullName;
        return res.ToString();
    }

I have to call ParentRequest.ParentRequest so that when I print the layout %logger it will print the class that calls the Log4Net log method instead of the Log4Net class of the method that called the Log method.