星期一

情景

早晨,项目组长来到小明身边,“有人反映咱们的项目有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 } View Code
 

 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 } View Code
 

开工

整理思路:发生错误时要执行自己需要的代码,只需要继承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 //创建一个线程池,将方法排入队列以便执行
10 ThreadPool.QueueUserWorkItem(o => { 11 while (true) 12 { 13 lock
(ExceptionStringQueue)14 { 15 //如果有错误信息写入日志 16 if (ExceptionStringQueue.Count >
0) 17 { 18 string exceptionString = ExceptionStringQueue.Dequeue(); 19 //
把错误信息写到日志文件中 20 using (System.IO.StreamWriter file = new System.IO.StreamWriter(
@"C:\Log.txt", true)) 21 { 22 file.WriteLine(exceptionString);// 直接追加文件末尾,换行 23
}24 } 25 else 26 { 27 Thread.Sleep(1000); 28 } 29 30 } 31 } 32 }); 33 }
34 35 36 //给外部提供方法,将错误信息写入队列 37 public static void Write(string exceptionString)
38 { 39 lock (ExceptionStringQueue) 40 { 41 //将错误信息添加到队列 42
ExceptionStringQueue.Enqueue(exceptionString);43 } 44 } 45 } View Code
 

把自己的过滤器注册到全局
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 } View Code
 

自定义错误测试
1 throw new Exception("自定义错误"); View Code
 



 OK,大功告成,以后就可以根据日志来找错误了。 

星期二

情景

早晨,项目组长又来到小明身边,”昨天我用了你的错误日志功能,还不错,但是你将日志写在文件中整理不是太方便,还存在共享冲突问题,你改下写到数据库中“ ”...“

分析

查看昨天写的代码



发现此处是一个变化点,有可能写到文件中,有可能写到数据库中,有可能......

不就是写到不同的地方么,简单,多态就能搞定了。

开工

依赖于抽象,而不依赖于具体

创建IWriteLog接口
1 public interface IWriteLog 2 { 3 //把错误信息写到相应的地方 4 void WriteLog(string
exceptionString);5 } View Code
创建WriteLogToText类实现接口,用来写入文本
1 public class WriteLogToText : IWriteLog 2 { 3 public void WriteLog(string
exceptionString)4 { 5 //将错误信息写入文本 6 } 7 } View Code
 创建WriteLogToSqlServer类实现接口,用来写入数据库
1 public class WriteLogToSqlServer : IWriteLog 2 { 3 public void WriteLog(
string exceptionString) 4 { 5 //将错误信息写入数据库 6 } 7 } View Code
 

对变化点进行修改
1 string exceptionString = ExceptionStringQueue.Dequeue(); 2 //依赖接口 3
IWriteLog writeLog =new WriteLogToSqlServer(); 4 //IWriteLog writeLog = new
WriteLogToText();5 //把错误信息写到相应的地方 6 writeLog.WriteLog(exceptionString);
 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 while (true) 19 { 20 lock
(ExceptionStringQueue)21 { 22 //如果有错误信息写入日志 23 if (ExceptionStringQueue.Count >
0) 24 { 25 string exceptionString = ExceptionStringQueue.Dequeue(); 26 //发布 27
foreach (var writeLog in writeLogList) 28 { 29
writeLog.WriteLog(exceptionString);30 } 31 } 32 else 33 { 34 Thread.Sleep(
1000); 35 } 36 } 37 } 38 39 }); 40 } 41 42 43 //给外部提供方法,将错误信息写入队列 44 public
static void Write(string exceptionString) { 45 lock (ExceptionStringQueue) 46 {
47 //将错误信息添加到队列 48 ExceptionStringQueue.Enqueue(exceptionString); 49 } 50 }
51 }
后期如果还需要写入其它地方或者去掉一个的话,只需要Add一个或者删除一行即可。当然,现在的代码还有很多可优化的地方,比如把通知者
(LogHelper)进行抽象,还可以通过配置文件加反射再次解耦。这里就不做过多介绍了。因为已经星期四了...

星期四

采用Log4Net

总结

观察者模式又叫发布-订阅模式,定义了一种一对多的依赖关系,让多个观察者同时监听同一个对象。当对象状态发生改变时,通知所订阅的观察者。

什么时候使用?

当一个对象改变同时需要改变其他对象,并且还不知道要改变多少对象。这时应该考虑观察者模式。