I’ve always liked the Castle.Core ILogger and ILoggerFactory abstraction. I don’t know why but I’ve never been a fan of log4net so I like to keep direct dependencies to it at arm’s length.

Looking into Autofac recently, I was trying to find a way of getting a logger property assigned as a component is created from the container in the same way as Windsor’s logging facility. The Autofac container has a Module/IModule extensibility which works well for the tasks a facility would have done.

Here’s the type of code I’m used to seeing from logging.

using Castle.Core.Logging;

namespace LoggingStudy {
    public interface IFoo {
        void Bar();
    }

    public class Foo : IFoo {
        public Foo() {
            Logger = NullLogger.Instance;
        }

        public ILogger Logger { get; set; }

        public void Bar() {
            Logger.Info("Bar called");
        }
    }
}


And here’s how you’d add a logging module to an Autofac builder.

using Autofac.Builder;

namespace LoggingStudy {

    class Program {
        static void Main(string[] args) {
            var builder = new ContainerBuilder();
            builder.RegisterModule(new LoggingModule());
            builder.Register<Foo>().As<IFoo>();

            var container = builder.Build();

            using (var scope = container.CreateInnerContainer()) {
                var foo = scope.Resolve<IFoo>();
                foo.Bar();
            }
        }
    }
}

And here’s an implementation of a LoggingModule that will use Castle’s ILoggerFactory to assign the ILogger typed public properties. Some of the code is based on a few threads in the discussion group.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Autofac;
using Autofac.Builder;
using Castle.Core.Logging;

namespace LoggingStudy {
    public class LoggingModule : Autofac.Builder.Module {
        protected override void Load(ContainerBuilder moduleBuilder) {
            // by default, use Castle's TraceSource based logger factory
            moduleBuilder.Register<TraceLoggerFactory>().As<ILoggerFactory>().ContainerScoped();

            // call CreateLogger in response to the request for an ILogger implementation
            moduleBuilder.Register((ctx, ps) => CreateLogger(ctx, ps)).As<ILogger>().FactoryScoped();
        }

        protected override void AttachToComponentRegistration(IContainer container, IComponentRegistration registration) {
            var implementationType = registration.Descriptor.BestKnownImplementationType;

            // build an array of actions on this type to assign loggers to member properties
            var injectors = BuildLoggerInjectors(implementationType).ToArray();

            // if there are no logger properties, there's no reason to hook the activated event
            if (!injectors.Any())
                return;

            // otherwise, whan an instance of this component is activated, inject the loggers on the instance
            registration.Activated += (s, e) => {
                foreach (var injector in injectors)
                    injector(e.Context, e.Instance);
            };
        }

        private static IEnumerable<Action<IContext, object>> BuildLoggerInjectors(Type componentType) {
            // Look for settable properties of type "ILogger"
            var loggerProperties = componentType
                .GetProperties(BindingFlags.SetProperty | BindingFlags.Public | BindingFlags.Instance)
                .Select(p => new {
                    PropertyInfo = p,
                    p.PropertyType,
                    IndexParameters = p.GetIndexParameters(),
                    Accessors = p.GetAccessors(false)
                })
                .Where(x => x.PropertyType == typeof(ILogger)) // must be a logger
                .Where(x => x.IndexParameters.Count() == 0) // must not be an indexer
                .Where(x => x.Accessors.Length != 1 || x.Accessors[0].ReturnType == typeof(void)); //must have get/set, or only set

            // Return an array of actions that resolve a logger and assign the property
            foreach (var entry in loggerProperties) {
                var propertyInfo = entry.PropertyInfo;

                yield return (ctx, instance) => {
                    var propertyValue = ctx.Resolve<ILogger>(new TypedParameter(typeof(Type), componentType));
                    propertyInfo.SetValue(instance, propertyValue, null);
                };
            }
        }

        private static ILogger CreateLogger(IContext context, IEnumerable<Parameter> parameters) {
            // return an ILogger in response to Resolve<ILogger>(componentTypeParameter)
            var loggerFactory = context.Resolve<ILoggerFactory>();
            var containingType = parameters.TypedAs<Type>();
            return loggerFactory.Create(containingType);
        }
    }
}

The TraceLoggerFactory is added as a default ILoggerFactory. It’s my favorite one and because it’s based on System.Diagnostics TraceSource it doesn’t require additional assemblies.

If you expect to Resolve from a container you’ll need to add a TypedParameter for typeof(Type), because the logger factory needs that information. It also doesn’t work to provide an ILogger constructor parameter as it’s written, but if ILogger properties are what you’re looking for it should do the job.