错误日志之观察者模式
星期一
情景
早晨,项目组长来到小明身边,“有人反映咱们的项目有bug” “什么bug?” “不知道,你添加一个日志模块自己看记录去。” ”...“
分析
在mvc全局过滤器中自己添加有异常过滤器。
global.asax
1 public class mvcapplication : system.web.httpapplication 2 { 3 protected void application_start() 4 { 5 arearegistration.registerallareas(); 6 //注册全局过滤器 7 filterconfig.registerglobalfilters(globalfilters.filters); 8 routeconfig.registerroutes(routetable.routes); 9 bundleconfig.registerbundles(bundletable.bundles); 10 } 11 }
filterconfig.cs
1 public class filterconfig 2 { 3 public static void registerglobalfilters(globalfiltercollection filters) 4 { 5 //向全局过滤器中添加异常过滤器 6 //只要你的项目出现了异常,就会执行过滤器里的onexception方法 7 filters.add(new handleerrorattribute()); 8 } 9 }
开工
整理思路:发生错误时要执行自己需要的代码,只需要继承iexceptionfilter,重写onexception方法,然后把自己的过滤器注册到全局即可。
创建过滤器,myexceptionfilter类
1 //因为微软已经提供了一个handleerrorattribute类(它其实也是继承了iexceptionfilter),所以我们只需继承它即可 2 public class myexceptionfilter: handleerrorattribute 3 { 4 //重写onexception方法 5 public override void onexception(exceptioncontext filtercontext) 6 { 7 base.onexception(filtercontext); 8 9 //把错误写到日志文件里面去 10 //思考:如果同时来了多个错误,一起向文件中写内容,就会发生同时访问同一个文件问题。你会怎么解决? 11 //提示:锁、队列 12 13 //loghelper类用来把错误写到日志里面去 14 loghelper.write(filtercontext.exception.tostring()); 15 16 } 17 }
loghelper类,用来把错误写到日志里面去
1 public class loghelper 2 { 3 //添加一个静态的异常信息队列,只要出现异常就写到队列中。 4 public static queue<string> exceptionstringqueue = new queue<string>(); 5 6 //第一次用到该类型时会执行静态构造函数,只被执行一次 7 static loghelper() { 8 //创建一个线程池,将方法排入队列以便执行 9 threadpool.queueuserworkitem(o=> { 10 lock (exceptionstringqueue) { 11 string exceptionstring = exceptionstringqueue.dequeue(); 12 //把错误信息写到日志文件中 13 using (system.io.streamwriter file = new system.io.streamwriter(@"c:\log.txt", true)) 14 { 15 file.writeline(exceptionstring);// 直接追加文件末尾,换行 16 } 17 } 18 }); 19 } 20 21 22 //给外部提供方法,将错误信息写入队列 23 public static void write(string exceptionstring) { 24 lock (exceptionstringqueue) 25 { 26 //将错误信息添加到队列 27 exceptionstringqueue.enqueue(exceptionstring); 28 } 29 } 30 }
把自己的过滤器注册到全局
1 public class filterconfig 2 { 3 public static void registerglobalfilters(globalfiltercollection filters) 4 { 5 //向全局过滤器中添加异常过滤器 6 //只要你的项目出现了异常,就会执行过滤器里的onexception方法 7 //filters.add(new handleerrorattribute()); 8 //把自己的过滤器注册到全局 9 filters.add(new myexceptionfilter()); 10 } 11 }
自定义错误测试
1 throw new exception("自定义错误");
ok,大功告成,以后就可以根据日志来找错误了。
星期二
情景
早晨,项目组长又来到小明身边,”昨天我用了你的错误日志功能,还不错,但是你将日志写在文件中整理不是太方便,还存在共享冲突问题,你改下写到数据库中“ ”...“
分析
查看昨天写的代码
发现此处是一个变化点,有可能写到文件中,有可能写到数据库中,有可能......
不就是写到不同的地方么,简单,多态就能搞定了。
开工
依赖于抽象,而不依赖于具体
创建iwritelog接口
1 public interface iwritelog 2 { 3 //把错误信息写到相应的地方 4 void writelog(string exceptionstring); 5 }
创建writelogtotext类实现接口,用来写入文本
1 public class writelogtotext : iwritelog 2 { 3 public void writelog(string exceptionstring) 4 { 5 //将错误信息写入文本 6 } 7 }
创建writelogtosqlserver类实现接口,用来写入数据库
1 public class writelogtosqlserver : iwritelog 2 { 3 public void writelog(string exceptionstring) 4 { 5 //将错误信息写入数据库 6 } 7 }
对变化点进行修改
1 threadpool.queueuserworkitem(o=> { 2 lock (exceptionstringqueue) { 3 string exceptionstring = exceptionstringqueue.dequeue(); 4 //依赖接口 5 iwritelog writelog = new writelogtosqlserver(); 6 //iwritelog writelog = new writelogtotext(); 7 //把错误信息写到相应的地方 8 writelog.writelog(exceptionstring); 9 10 } 11 });
ok,大功告成,又可以去美滋滋了...
星期三
情景
早晨,项目组长再一次来到小明身边,”经过我的思考,我觉得把错误信息同时写到文本和数据库中比较好“ ”为什么?“ “需求” “...”
分析
错误信息有可能要写到不同的地方,而且不知道有多少地方,说不定明天又加了一个redis、后天再加一个....
这时候我们可以考虑创建一个集合来保存都需要写到那些地方去。(这里插一句:设计模式只是一种思想,实现方式肯定是不唯一的,但是思想是精髓,不能说这个代码是这个模式,换一种方式实现就不是这个模式了。)
然后依次写入即可。
开工
对loghelper进行修改
1 public class loghelper 2 { 3 //添加一个静态的异常信息队列,只要出现异常就写到队列中。 4 public static queue<string> exceptionstringqueue = new queue<string>(); 5 6 //定义一个集合来存放所有的 观察者, 7 public static ilist<iwritelog> writeloglist = new list<iwritelog>(); 8 9 //第一次用到该类型时会执行静态构造函数,只被执行一次 10 static loghelper() { 11 12 //观察(订阅) 13 writeloglist.add(new writelogtosqlserver()); 14 writeloglist.add(new writelogtotext()); 15 16 //创建一个线程池,将方法排入队列以便执行 17 threadpool.queueuserworkitem(o=> { 18 lock (exceptionstringqueue) { 19 string exceptionstring = exceptionstringqueue.dequeue(); 20 21 //发布 22 foreach (var writelog in writeloglist) 23 { 24 writelog.writelog(exceptionstring); 25 } 26 27 } 28 }); 29 } 30 31 32 //给外部提供方法,将错误信息写入队列 33 public static void write(string exceptionstring) { 34 lock (exceptionstringqueue) 35 { 36 //将错误信息添加到队列 37 exceptionstringqueue.enqueue(exceptionstring); 38 } 39 } 40 }
后期如果还需要写入其它地方或者去掉一个的话,只需要add一个或者删除一行即可。当然,现在的代码还有很多可优化的地方,比如把通知者(loghelper)进行抽象,还可以通过配置文件加反射再次解耦。这里就不做过多介绍了。因为已经星期四了...
星期四
采用log4net
总结
观察者模式又叫发布-订阅模式,定义了一种一对多的依赖关系,让多个观察者同时监听同一个对象。当对象状态发生改变时,通知所订阅的观察者。
什么时候使用?
当一个对象改变同时需要改变其他对象,并且还不知道要改变多少对象。这时应该考虑观察者模式。