日志系统实战(一)—AOP静态注入

背景

近期在写日志系统,需要在运行时在函数内注入日志记录,并附带函数信息,这时就想到用Aop注入的方式。

AOP分动态注入和静态注入两种注入的方式。

动态注入方式

  1. 利用Remoting的ContextBoundObject或MarshalByRefObject。
  2. 动态代理(反射),很多AOP框架都用这种方式。
  3. MVC的filter,也是反射。

第一种性能太差,必须继承基类等,所以不考虑。

第二种为了记日志,大量动态生成代理类,性能损耗不小,不建议生产环节推荐。

第三种MVC只能进行UI层的拦截,其他层需要实现自行实现动态拦截,跟第二种实现方式一样。

静态注入方式

基于Net的IL语言层级进行注入,性能损耗可以忽略不计,Net使用最多的Aop框架PostSharp采用的即是这种方式。

技术实现

声明Attribute

public class WeaveSign:Attribute
{
}
public class WeaveAction : Attribute
{
        
}
public class Log : WeaveAction
{
        public static void OnActionBefore(MethodBase mbBase)
        {
           //mbBase 要注入方法的所有信息
            var t = mbBase.GetParameters();
        LogManager.Record();
       }
}        

标记需要注入的方法

[Log]
public static string GetUserName(int userId)
{
        return "Vidar";
}

IL注入关键的地方,这里使用Mono.Cecil进行IL分析和写入。

public static void AutoWeave()
{
    var assemblies = BuildManager.GetReferencedAssemblies();
    var result =
    assemblies.Cast<Assembly>().Where(assembly =>!assembly.FullName.StartsWith("System.", StringComparison.OrdinalIgnoreCase));

    Weave(result);
}


private static void Weave(IEnumerable<Assembly> assemblyList)
        {
            //assemblyList要注入的程序集列表。
            foreach (var item in assemblyList)
            {
                var filepath = item.CodeBase.Substring(8, item.CodeBase.Length - 8);
                //读取程序集
                var assembly = AssemblyDefinition.ReadAssembly(filepath);

                //获取WeaveSign类型的类型注入标记
                var types = assembly.MainModule.Types.Where(n => n.CustomAttributes.Any(y => y.AttributeType.Resolve().Name == "WeaveSign"));

                foreach (var type in types)
                {
                    foreach (var method in type.Methods)
                    {
                        //获取WeaveAction类型的方法注入标记
                        var attrs =
                            method.CustomAttributes.Where(y => y.AttributeType.Resolve().BaseType.Name == "WeaveAction");
                        foreach (var attr in attrs)
                        {
                            //解析类型
                            var resolve = attr.AttributeType.Resolve();
                            //获取IL容器
                            var ilProcessor = method.Body.GetILProcessor();
                            var firstInstruction = ilProcessor.Body.Instructions.First();
                            //找到标记类型的OnActionBefore方法。
                            var onActionBefore = resolve.GetMethods().Single(n => n.Name == "OnActionBefore");
                            //创建System.Reflection.MethodBase.GetCurrentMethod()方法引用
                            var mfReference=assembly.MainModule.Import(typeof (System.Reflection.MethodBase).GetMethod("GetCurrentMethod"));

                            //注入到IL(调用GetCurrentMethod,入栈)
                            ilProcessor.InsertBefore(firstInstruction,
                                ilProcessor.Create(OpCodes.Call,mfReference));
                            //创建调用(call)标记类型的方法OnActionBefore
                            ilProcessor.InsertBefore(firstInstruction, ilProcessor.Create(OpCodes.Call, onActionBefore));
                        }
                    }
                }
                if (types.Any())
                {
                    //写入程序集
                    assembly.Write(filepath);
                }
            }
        }    

为了演示简便,没有在MsBuild期间进行注入,而是单写了个测试页面直接触发进行代码注入。

  protected void Page_Load(object sender, EventArgs e)
  {
          CodeWeaver.AutoWeave();
  }

运行成功后,反编译注入的DLL看到的IL代码:

  .method public hidebysig static string GetUserName(int32 userId) cil managed
{
    .custom instance void TestLibrary.Log::.ctor()
    .maxstack 1
    .locals init (
        [0] string str)
    L_0000: call class [mscorlib]System.Reflection.MethodBase [mscorlib]System.Reflection.MethodBase::GetCurrentMethod()
    L_0005: call void TestLibrary.Log::OnActionBefore(class [mscorlib]System.Reflection.MethodBase)
    L_000a: nop 
    L_000b: ldstr "Vidar"
    L_0010: stloc.0 
    L_0011: br.s L_0013
    L_0013: ldloc.0 
    L_0014: ret 
}

 

C#

[Log]
public static string GetUserName(int userId)
{
    Log.OnActionBefore(MethodBase.GetCurrentMethod());
    return "Vidar";
}

 

Mono.Cecil地址 http://www.mono-project.com/docs/tools+libraries/libraries/Mono.Cecil/

你可能感兴趣的:(日志系统)