錯誤日誌之觀察者模式
- 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
總結
觀察者模式又叫發布-訂閱模式,定義了一種一對多的依賴關係,讓多個觀察者同時監聽同一個對象。當對象狀態發生改變時,通知所訂閱的觀察者。
什麼時候使用?
當一個對象改變同時需要改變其他對象,並且還不知道要改變多少對象。這時應該考慮觀察者模式。