Trying to make a Logging Interceptor for StructureMap using DynamicProxy

2.2k views Asked by At

I'm trying to log calls from the UI (DNN module) to some of various services it uses, in a effort to profile how people are interacting with the site. I'm using StructureMap 2.5.3.0 and Log4Net

I got things working well on individual class/instance pairs, but I have to configure things like this:

ObjectFactory.Configure(ce =>
        ce.ForRequestedType<IRegService>()
          .TheDefaultIsConcreteType<RegService>()
          .EnrichWith(LoggingEnrichment.InterfaceLogger<IRegService>));

Having the IRegService twice felt a bit messy, but I can live with it.

The logging is implemented like this:

public class LoggingEnrichment
{
    public static object InterfaceLogger<TInterface>(object concrete)
    {
        return InterfaceLogger(typeof(TInterface), concrete);
    }

    public static object InterfaceLogger(Type iinterface, object concrete)
    {
        var dynamicProxy = new ProxyGenerator();
        return dynamicProxy.CreateInterfaceProxyWithTarget(iinterface, concrete, new LogInterceptor());
    }
}

public class LogInterceptor : IInterceptor
{
    public void Intercept(IInvocation invocation)
    {
        var watch = new Stopwatch();
        watch.Start();
        invocation.Proceed();
        watch.Stop();
        ILog logger = LogManager.GetLogger(typeof(LogInterceptor));
        var sb = new StringBuilder();
        sb.AppendFormat("Calling: {0}.{1}\n", invocation.InvocationTarget.GetType(), invocation.MethodInvocationTarget.Name);
        var param = invocation.Method.GetParameters();
        if (param.Length > 0) sb.Append("With:\n");
        for (int i = 0; i < param.Length; i++)
        {
            sb.AppendFormat("\t{0}\n\t\t{1}", param[i].Name, invocation.GetArgumentValue(i));
        }
        if(invocation.Method.ReturnType != typeof(void))
        {
            sb.AppendFormat("Returning: {0}\n", invocation.ReturnValue ?? "null");
        }
        sb.AppendFormat("In: {0}ms\n", watch.ElapsedMilliseconds);
        logger.Debug(sb.ToString());
    }
}

This works, but has a couple issues:

  1. I have to manually configure each service <-> interface pair
  2. I only want to wire up the logging when the service is called from the UI

I tried to get around this by implementing a TypeInterceptor for StructureMap:

public class ApplicationRegistry : Registry
{
    public ApplicationRegistry()
    {
        RegisterInterceptor(new LoggingInterceptor());
        Scan(scanner =>
        {
            scanner.TheCallingAssembly();
            var codeBase = System.Reflection.Assembly.GetExecutingAssembly().CodeBase.Replace("file:///", String.Empty);
            codeBase = codeBase.Substring(0, codeBase.LastIndexOf("/"));
            scanner.AssembliesFromPath(codeBase);
            scanner.WithDefaultConventions();
            scanner.LookForRegistries();
        });
    }
}

public class LoggingInterceptor :TypeInterceptor
{
    public object Process(object target, IContext context)
    {
        var newTarget = target;
        if (context.BuildStack.Current != null && context.BuildStack.Current.RequestedType != null)
        {
            newTarget = LoggingEnrichment.InterfaceLogger(context.BuildStack.Current.RequestedType, target);
        }
        return newTarget;
    }

    public bool MatchesType(Type type)
    {
        return type.Name.EndsWith("Service", StringComparison.OrdinalIgnoreCase);
    }
}

But I'm getting a problem with that where the call to Process is giving me a class that doesn't implement the interface defined by the build context. This has resulted in having to change the implementation of the InterfaceLogger to

    public static object InterfaceLogger(Type iinterface, object concrete)
    {
        if(!iinterface.IsAssignableFrom(concrete.GetType())) return concrete;
        var dynamicProxy = new ProxyGenerator();
        var interfaceProxy = dynamicProxy.CreateInterfaceProxyWithTarget(iinterface, concrete, new LogInterceptor());
        return interfaceProxy;
    }

A breakpoint on the return interfaceProxy; is never reached, this indicates that context.BuildStack.Current.RequestedType isn't returning the right interface. The odd thing is that all my classes seem to be injected correctly.

Also, even if this was working I'd still have the issue of only wanting to intercept the calls from the UI layer.

I'm looking for a way my first 2 issues, and also what I'm doing wrong with the TypeInterceptor

1

There are 1 answers

0
daxsorbito On BEST ANSWER

I got around with this by using Convention. Below are the steps I did to achieve this.

First I did a scan on my specified assembly where I would attach my decorator.

x.Scan(scanner =>
                {
                    scanner.Assembly("MyProject.Services"); // Specific assemblyname
                    scanner.Convention<ServiceRegistrationConvention>();
                    scanner.WithDefaultConventions();
                    scanner.LookForRegistries();

                });

Then I created a Convention class. I actually got it from this thread Decorating a generic interface with Structuremap and did some revision based on your implementation.

And lastly this is the Convention class.

 public class ServiceRegistrationConvention : IRegistrationConvention
    {
        public void Process(Type type, Registry registry)
        {
            var handlerInterfaces = (from t in type.GetInterfaces()
                                     where 
                                        (t.Namespace.StartsWith("MyProject.UIServices", StringComparison.OrdinalIgnoreCase)
                                        || t.Namespace.StartsWith("MyProject.Services", StringComparison.OrdinalIgnoreCase))
                                     select t);


            foreach (var handler in handlerInterfaces)
            {
                registry.For(handler)
                    .EnrichWith((ctx, orig) => LoggingEnrichment.InterfaceLogger(handler, orig));
            }

        }
    }

I use the same LoggingEnrichment class that you have.

Hope this solves your mentioned issues.