错误日志之观察者模式

  • 2019 年 10 月 3 日
  • 筆記

星期一

情景

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

总结

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

什么时候使用?

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