Log4Net Integration with Unity IoC Container
The Castle Project has a great logging integration facility that lets you integrate Log4Net (or NLog) into your dependency resolution such that if you declare a dependency on ILogger (Castles wrapper around Log4Net.ILog), the dependancy that gets injected is initialized with the type into which it is being injected. If you understand how Log4Net logger names work, you’ll know why this is important.
But on my current project, I am not using Castle; I am using Microsoft’s Unity IoC container. Figuring out how to do the same thing in Unity took me most of a day and I didn’t like my solution, so I posted a question on Stack Overflow and got a response that pointed me to exactly what I wanted. The links were to a blog post and ultimately to an obscure thread on the Unity – Patern’s and Practices forum. I find that forums are notorious for not being there for the long haul, and the blog did not provide any details on its own, so I am re-posting it it all here in a slightly cleaned up form for posterity’s sake.
Implementing this in Unity requires three steps.
- Creating a Build Tracking Extension
- Creating a Logger Creation Extension
- Wire the extensions into Unity
The Build Tracking Extension
The first step is to a create Unity extension that installs a build tracking strategy. This strategy uses a policy to keep track of the stack of dependencies being built as part of handling the current resolution request. This is needed so that when resolving the logger dependency, we can consult the stack to see what type the dependency is being resolved for.
Here is the code, pretty much taken straight from the aforementioned post.
using System; using System.Collections.Generic; using Microsoft.Practices.ObjectBuilder2; using Microsoft.Practices.Unity; using Microsoft.Practices.Unity.ObjectBuilder; public class BuildTracking : UnityContainerExtension { protected override void Initialize() { Context.Strategies.AddNew<BuildTrackingStrategy>(UnityBuildStage.TypeMapping); } public static IBuildTrackingPolicy GetPolicy(IBuilderContext context) { return context.Policies.Get<IBuildTrackingPolicy>(context.BuildKey, true); } public static IBuildTrackingPolicy SetPolicy(IBuilderContext context) { IBuildTrackingPolicy policy = new BuildTrackingPolicy(); context.Policies.SetDefault(policy); return policy; } } public class BuildTrackingStrategy : BuilderStrategy { public override void PreBuildUp(IBuilderContext context) { var policy = BuildTracking.GetPolicy(context) ?? BuildTracking.SetPolicy(context); policy.BuildKeys.Push(context.BuildKey); } public override void PostBuildUp(IBuilderContext context) { IBuildTrackingPolicy policy = BuildTracking.GetPolicy(context); if ((policy != null) && (policy.BuildKeys.Count > 0)) { policy.BuildKeys.Pop(); } } } public interface IBuildTrackingPolicy : IBuilderPolicy { Stack<object> BuildKeys { get; } } public class BuildTrackingPolicy : IBuildTrackingPolicy { public BuildTrackingPolicy() { BuildKeys = new Stack<object>(); } public Stack<object> BuildKeys { get; private set; } } |
The Logger Creation Extension
With the build tracking extension in place, it is now possible to create a logger creation extension that will use it to create an instance of ILog. (This implemenation is not generic like the Castle implemenation but specific to Log4Net).
Again here is the code, pretty much straight from the forum thread. All I have done is incorporated the patch (also from the thread) to handle direct resolution requests for an ILog instance.
using System; using System.Diagnostics; using System.Linq; using Microsoft.Practices.ObjectBuilder2; using Microsoft.Practices.Unity; using Microsoft.Practices.Unity.ObjectBuilder; using Log4Net; public class LogCreation : UnityContainerExtension { protected override void Initialize() { Context.Strategies.AddNew<LogCreationStrategy>(UnityBuildStage.PreCreation); } } public class LogCreationStrategy : BuilderStrategy { public bool IsPolicySet { get; private set; } public override void PreBuildUp(IBuilderContext context) { Type typeToBuild = context.BuildKey.Type; if (typeof(ILog).Equals(typeToBuild)) { if (context.Policies.Get<IBuildPlanPolicy>(context.BuildKey) == null) { Type typeForLog = LogCreationStrategy.GetLogType(context); IBuildPlanPolicy policy = new LogBuildPlanPolicy(typeForLog); context.Policies.Set<IBuildPlanPolicy>(policy, context.BuildKey); IsPolicySet = true; } } } public override void PostBuildUp(IBuilderContext context) { if (IsPolicySet) { context.Policies.Clear<IBuildPlanPolicy>(context.BuildKey); IsPolicySet = false; } } private static Type GetLogType(IBuilderContext context) { Type logType = null ; IBuildTrackingPolicy buildTrackingPolicy = BuildTracking.GetPolicy(context); if ((buildTrackingPolicy != null) && (buildTrackingPolicy.BuildKeys.Count >= 2)) { logType = ((NamedTypeBuildKey)buildTrackingPolicy.BuildKeys.ElementAt(1)).Type; } else { StackTrace stackTrace = new StackTrace(); //first two are in the log creation strategy, can skip over them for (int i = 2; i < stackTrace.FrameCount; i++) { StackFrame frame = stackTrace.GetFrame(i); logType = frame.GetMethod().DeclaringType; if (!logType.FullName.StartsWith("Microsoft.Practices")) { break; } } } return logType; } } public class LogBuildPlanPolicy : IBuildPlanPolicy { public LogBuildPlanPolicy(Type logType) { LogType = logType; } public Type LogType { get; private set; } public void BuildUp(IBuilderContext context) { if (context.Existing == null) { ILog log = LogManager.GetLogger(LogType); context.Existing = log; } } } |
Wiring It All Up
Lastly, to wire this up to your container all you need is the following (also stolen) code:
var container = new UnityContainer() .AddNewExtension<BuildTracking>() .AddNewExtension<LogCreation>() .Register<MyClassWithLoggerDependency>(); var myClass = container.Resolve<MyClassWithLoggerDependency>(); //Or var myLogger = container.Resolve<ILog>(); //return logger with a name of this.GetType().Fullname |
Hope this code helps you as much as it did me. Many thanks to wageoghe, David Keaveny, Marco and Scott!