錯誤日誌之觀察者模式
星期一
情景
早晨,專案組長來到小明身邊,“有人反映咱們的專案有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 }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 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
總結
觀察者模式又叫釋出-訂閱模式,定義了一種一對多的依賴關係,讓多個觀察者同時監聽同一個物件。當物件狀態發生改變時,通知所訂閱的觀察者。
什麼時候使用?
當一個物件改變同時需要改變其他物件,並且還不知道要改變多少物件。這時應該考慮觀察者模式。