Saturday, June 16, 2012

Injection Logger in Orchard


Introduction:

Orchard using Aufofac as DI container. The blog just try to explain one of those usage: Inject Logging. In any system logging is a very basis function and normally when we need to log something, we either create a new Logger instance or get a global instance to do the logging. while, Orchard using a different way to get the Logger instance. Whenever a class requires a Logger instance, all it needs to do is to declare a ILogger property, that’s it. And later, in your class, you can use this property to Logging at anytime. So, how it happening? Who and when this property been set?

Register Logger:

When Orchard web application startup, the OrchardStarter will be used to do most of the registration work.
public static IContainer CreateHostContainer(Action<ContainerBuilder> registrations) {
      var builder = new
ContainerBuilder();
      builder.RegisterModule(new
CollectionOrderModule());
      builder.RegisterModule(new
LoggingModule());

}
The LoggingModule is registered to the Autofac ContainerBuilder. Let’s look at the detail of LoggingModule
public class LoggingModule : Module {
}
It inherites from the Autofac.Module. Autofac.Module provide the method that you can register a group of types in one place.But we only want to register the Logger, why use the Module? The Module provide extra methods that can hooked to the whole registration event so that we can has the chance to inject Logger intance to other object’s property. This method used in LoggingModule is  
protected override void AttachToComponentRegistration(IComponentRegistry componentRegistry, IComponentRegistration registration) {
           var implementationType = registration.Activator.LimitType;
           // 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);
           };

}
AttachToComponentRegistration will be used by the Autofac to attach to both the existing registered components and the  future components that will be registered to this ContainerBuilder. In the code with red color, we can see when a component will be activated(or resolved), we will run each injector to this new created instance. What is the injector? Let’s see the detail of BuillLoggerInjectors
private IEnumerable<Action<IComponentContext, 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) => {
                   string component = componentType.ToString();
                   var logger = _loggerCache.GetOrAdd(component, key => ctx.Resolve<
ILogger>(new TypedParameter(typeof(Type), componentType)));
                   propertyInfo.SetValue(instance, logger, null);
               };
           }
       }

In this method, it first find all the properties that type is ILogger and is not indexer and it can  be set. Next for each of these properties, it will create a Action that set the property to ILogger instance that from local cache or resolve from the context. In the Load method of LoggingModule, it register the LoggerFactory and the ILogger
protected override void Load(ContainerBuilder moduleBuilder) {
           // by default, use Orchard's logger that delegates to Castle's logger factory
           moduleBuilder.RegisterType<
CastleLoggerFactory>().As<ILoggerFactory>().InstancePerLifetimeScope();
moduleBuilder.RegisterType<
OrchardLog4netFactory>().As<Castle.Core.Logging.ILoggerFactory>().InstancePerLifetimeScope();
           // call CreateLogger in response to the request for an ILogger implementation
           moduleBuilder.Register(CreateLogger).As<
ILogger>().InstancePerDependency();
}

We can see register ILogger using a delegate. Why? Because we don’t want to create Logger for each activated instance, for the same type of instance, we just want to use the same Logger instance.
private static ILogger CreateLogger(IComponentContext 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.CreateLogger(containingType);
}

We can see the loggerFactory.CreateLogger passed in the type of the current activated type.

No comments: