尝试使用 DynamicProxy 为 StructureMap 制作日志拦截器

发布于 2024-10-16 12:48:17 字数 3949 浏览 4 评论 0原文

我正在尝试记录从 UI(DNN 模块)到它使用的一些各种服务的调用,以分析人们如何与网站交互。我正在使用 StructureMap 2.5.3.0 和 Log4Net

我在各个类/实例对上运行良好,但我必须配置如下内容:

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

两次使用 IRegService 感觉有点混乱,但我可以与它共存。

日志记录是这样实现的:

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());
    }
}

这可行,但有几个问题:

  1. 我必须手动配置每个服务<->接口对
  2. 我只想在从 UI 调用服务时连接日志记录

我试图通过为 StructureMap 实现 TypeInterceptor 来解决这个问题:

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);
    }
}

但是我遇到了一个问题,其中调用 Process 给了我一个没有实现构建上下文定义的接口的类。这导致必须将 InterfaceLogger 的实现更改为

    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;
    }

return interfaceProxy; 上的断点永远不会到达,这表明 context.BuildStack.Current。 RequestedType 未返回正确的接口。奇怪的是,我的所有课程似乎都被正确注入。

另外,即使这有效,我仍然会遇到只想拦截来自 UI 层的调用的问题。

我正在寻找解决前两个问题的方法,以及我在 TypeInterceptor 上做错了什么

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

雄赳赳气昂昂 2024-10-23 12:48:17

我通过使用约定来解决这个问题。以下是我为实现此目标所做的步骤。

首先,我对指定的程序集进行了扫描,我将在其中附加装饰器。

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

                });

然后我创建了一个 Convention 类。我实际上是从这个线程 Decorating a generic interface with Structuremap 得到的,并做了一些根据您的实施进行修订。

最后是会议课程。

 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));
            }

        }
    }

我使用与您相同的 LoggingEnrichment 类。

希望这能解决您提到的问题。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文