錯誤日誌之觀察者模式

  • 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

總結

觀察者模式又叫發佈-訂閱模式,定義了一種一對多的依賴關係,讓多個觀察者同時監聽同一個對象。當對象狀態發生改變時,通知所訂閱的觀察者。

什麼時候使用?

當一個對象改變同時需要改變其他對象,並且還不知道要改變多少對象。這時應該考慮觀察者模式。