背景:现在公司整体在做监控平台,要求把各个部分的细节都记录下来,在前台页面上有所显示,所以现在需要做的就是一个监控日志的记录工作,今天讲的就是渲染监控日志的例子。
现状:当前的渲染程序没有为监控日志记录预留代码接入口,因此要新增加监控日志势必要增加新的代码。但是如果只是单单在已有逻辑中新增日志记录的代码似乎不太合理:因为记录日志的代码很多都是一样的,直接插入代码有很多重复代码;原有代码已经上线并且运行良好,新增代码会破坏原有封装,甚至增加出错的可能;再者从设计的角度来说,记录日志不属于任何一个功能的,新增日志和每个方法的执行意图格格不入,因此需要一种新的记录日志方式。
解决方案:
数据结构:设计一个通用的方法日志记录类,包含ID,方法名,执行时间长度,日志结果(正常记录正常信息,异常记录错误信息),日志结果是否为异常标志位等等,还有List<string>包含方法中用到的参变量名称,List<object>方法中的参变量的值(实现序列化接口或特性),从而实现可以记录方法上下文信息并帮助排错。所有方法执行情况日志都用这个类,形成一个统一通用的方法执行状态信息记录的类。
记录方式:对于所有需要进行日志记录的方法的采用对该方法所在的类进行动态代理的方式处理。在代理方法中的在原有方法的前后新增日志记录,动态代理采用装饰器模式实现通常称为AOP,AOP方面的内容请参阅: https://msdn.microsoft.com/zh-cn/library/dn574804.aspx 。以上是对已经存在的类的方法进行日志记录采用动态代理该类,对于方法内部的某个地方进行日志记录的,采用动态代理Stopwatch来实现。因为方法内部的日志一般都要统计时间长度,动态代理Stopwatch的Start方法和Stop方法,在Start和Stop调用的时后记录出方法的执行时间和中间参变量的值。
获取参变量:对于方法的参数获取比较容易,动态代理时会把参数传递到代理方法中,可以用对象池将该参数的句柄获取并保存,为防止对象池持有参变量使其不能在退出方法时被垃圾回收器回收,采用弱引用的方式持有所有参变量。对于方法内部的局部变量,采用代理方法中提前实例化保存到对象池方式。对象池采用字典实现,对象池的key用字符串表示,记录的是方法名称+变量名+线程上下文哈希值的方式,实现多线程中记录同一参数不同参数key的后缀(线程上下文哈希值)和不同参数值的记录,这样就可以在方法的外部提前获取局部变量的值并输出到日志。
对象池实现:上文提到对象池采用字典存储,但是对象池何时进行垃圾回收、清理垃圾数据呢?这时我们要自己定义一个添加对象到对象池的方法,在这个方法的第一部分就是清理垃圾数据,这样每新增一笔数据就删除旧数据,从而保证对象池的数据有效性。设置局部变量时要在方法执行前的代理方法中就添加,在原有代码中再添加的时候先从对象池中去取,如果有就用已经用原代码前添加进去的值,没有或者已经被垃圾回收了则重新生成新的数据。
源代码: