1. 程式人生 > >錯誤日誌之觀察者模式

錯誤日誌之觀察者模式

星期一

情景

早晨,專案組長來到小明身邊,“有人反映咱們的專案有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             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

總結

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

什麼時候使用?

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