1. 程式人生 > >重學c#系列——盛派自定義異常原始碼分析(八)

重學c#系列——盛派自定義異常原始碼分析(八)

### 前言 接著異常七後,因為以前看過盛派這塊程式碼,正好重新整理一下。 ### 正文 #### BaseException 首先看下BaseException 類: 繼承:public class BaseException : ApplicationException 這個ApplicationException 前文中提及到,文件地址:https://docs.microsoft.com/zh-cn/dotnet/api/system.applicationexception?view=netcore-3.1 這裡面有一個問題就是文件裡提示的是不應使用Application,應該使用Exception。 網上別人給的不建議使用的說法是: ``` 如果設計的應用程式需要建立自己的異常,則建議從Exception類派生自定義異常。最初認為自定義異常應該來自ApplicationException類; 然而在實踐中,這並沒有被發現增加顯著價值。 ``` 好的,那麼來看下其例項方法。 ``` /// /// BaseException /// /// 異常訊息 /// 內部異常資訊 /// 是否已經使用WeixinTrace記錄日誌,如果沒有,BaseException會進行概要記錄 public BaseException(string message, Exception inner, bool logged = false) : base(message, inner) { if (!logged) { SenparcTrace.BaseExceptionLog(this); } } ``` 那麼看下BaseExceptionLog幹了什麼吧。 ``` /// /// BaseException 日誌 ///
/// public static void BaseExceptionLog(Exception ex) { BaseExceptionLog(new BaseException(ex.Message, ex, false)); } /// /// BaseException 日誌 /// /// public static void BaseExceptionLog(BaseException ex) { if (Config.IsDebug) { using (SenparcTraceItem senparcTraceItem = new SenparcTraceItem(_logEndActon, "BaseException", null)) { senparcTraceItem.Log(ex.GetType().Name); senparcTraceItem.Log("Message:{0}", ex.Message); senparcTraceItem.Log("StackTrace:{0}", ex.StackTrace); if (ex.InnerException != null) { senparcTraceItem.Log("InnerException:{0}", ex.InnerException.Message); senparcTraceItem.Log("InnerException.StackTrace:{0}", ex.InnerException.StackTrace); } if (OnBaseExceptionFunc != null) { try { OnBaseExceptionFunc(ex); } catch { } } } } } ``` 上面的大體內容是,建立了一個SenparcTraceItem 的實體類,值得注意的是SenparcTraceItem 使用了using。 證明了什麼呢?證明了SenparcTraceItem 是一個非託管,那麼自己實現了回收機制,可檢視我的非託管程式裡面。 senparcTraceItem.Log 做了什麼呢? ``` public void Log(string messageFormat, params object[] param) { Log(messageFormat.FormatWith(param)); } public void Log(string message) { if (Content != null) { Content += Environment.NewLine; } Content = Content + "\t" + message; } ``` 只是做一些字元拼接,那麼問題來了,它是如何寫道持久化檔案中的呢? ``` public void Dispose() { _logEndAction?.Invoke(this); } ``` 當其回收的時候會觸發_logEndAction,那麼這個_logEndAction委託做啥呢?這個好像是我們傳進去的吧,找到我們穿進去的值。 ``` /// /// 結束日誌記錄 ///
protected static Action _logEndActon = delegate(SenparcTraceItem traceItem) { string logStr2 = traceItem.GetFullLog(); SenparcMessageQueue senparcMessageQueue = new SenparcMessageQueue(); string str = SystemTime.Now.Ticks.ToString(); int num = traceItem.ThreadId; string str2 = num.ToString(); num = logStr2.Length; string key = str + str2 + num.ToString(); senparcMessageQueue.Add(key, delegate { _queue(logStr2); }); }; ``` 邏輯就是GetFullLog: ``` /// /// 獲取完整單條日誌的字串資訊 ///
public string GetFullLog() { return string.Format("[[[{0}]]]\r\n[{1}]\r\n[執行緒:{2}]\r\n{3}\r\n\r\n", Title, DateTime.ToString("yyyy/MM/dd HH:mm:ss.ffff"), ThreadId, Content); } ``` 然後加入到佇列中: ``` SenparcMessageQueue senparcMessageQueue = new SenparcMessageQueue(); senparcMessageQueue.Add(key, delegate{_queue(logStr2);}); ``` 它的一個佇列設計是這樣的: 將其加在一個字典中: ``` public SenparcMessageQueueItem Add(string key, Action action) { lock (MessageQueueSyncLock) { SenparcMessageQueueItem senparcMessageQueueItem = new SenparcMessageQueueItem(key, action, null); MessageQueueDictionary.TryAdd(key, senparcMessageQueueItem); return senparcMessageQueueItem; } } ``` 自定義字典: ``` public static MessageQueueDictionary MessageQueueDictionary = new MessageQueueDictionary(); ``` 它的消費函式在另一個執行緒中,這裡就不介紹了。 值得關心的是_queue,畢竟我們要知道我們的log列印到了什麼地方: ``` /// /// 佇列執行邏輯 /// protected static Action _queue = async delegate(string logStr) { IBaseObjectCacheStrategy cache = Cache; TimeSpan retryDelay = default(TimeSpan); using (await cache.BeginCacheLockAsync("SenparcTraceLock", "", 0, retryDelay)) { string text = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "App_Data", "SenparcTraceLog"); if (!Directory.Exists(text)) { Directory.CreateDirectory(text); } string text2 = Path.Combine(text, string.Format("SenparcTrace-{0}.log", SystemTime.Now.ToString("yyyyMMdd"))); if (AutoUnlockLogFile) { for (int i = 0; i < 3; i++) { if (!FileHelper.FileInUse(text2)) { break; } GC.Collect(); GC.WaitForPendingFinalizers(); DateTimeOffset now = SystemTime.Now; if (i < 2) { do { retryDelay = SystemTime.NowDiff(now); } while (retryDelay.TotalMilliseconds < 100.0); } } } try { using (FileStream fs = new FileStream(text2, FileMode.OpenOrCreate)) { using (StreamWriter sw = new StreamWriter(fs)) { fs.Seek(0L, SeekOrigin.End); await sw.WriteAsync(logStr); await sw.FlushAsync(); } } } catch (Exception) { } if (OnLogFunc != null) { try { OnLogFunc(); } catch { } } } }; ``` 根據上述中,可得:儲存在App_Data/SenparcTraceLog目錄下。 好的那麼來看BaseException 的子類吧。 #### WeixinException ``` public class WeixinException : BaseException ``` 看下它的例項化: ``` /// /// WeixinException /// /// 異常訊息 /// 內部異常資訊 /// 是否已經使用WeixinTrace記錄日誌,如果沒有,WeixinException會進行概要記錄 public WeixinException(string message, Exception inner, bool logged = false) : base(message, inner, true/* 標記為日誌已記錄 */) { if (!logged) { //WeixinTrace.Log(string.Format("WeixinException({0}):{1}", this.GetType().Name, message)); WeixinTrace.WeixinExceptionLog(this); } } ``` 他首先乾的是就是讓它的基類不答應log,交給它自己處理。 WeixinTrace 實際上繼承SenparcTrace,這裡可以猜測到基本就是在SenparcTrace封裝一層了。 ``` public class WeixinTrace : SenparcTrace ``` 實際上不出所料: ``` /// /// WeixinException 日誌 /// /// public static void WeixinExceptionLog(WeixinException ex) { if (!Config.IsDebug) { return; } using (var traceItem = new SenparcTraceItem(SenparcTrace._logEndActon, "WeixinException")) { traceItem.Log(ex.GetType().Name); traceItem.Log("AccessTokenOrAppId:{0}", ex.AccessTokenOrAppId); traceItem.Log("Message:{0}", ex.Message); traceItem.Log("StackTrace:{0}", ex.StackTrace); if (ex.InnerException != null) { traceItem.Log("InnerException:{0}", ex.InnerException.Message); traceItem.Log("InnerException.StackTrace:{0}", ex.InnerException.StackTrace); } } if (OnWeixinExceptionFunc != null) { try { OnWeixinExceptionFunc(ex); } catch { } } } ``` ### 結 上文只是一個簡單的原始碼檢視,如需檢視原始碼,可以去github搜尋senparc,是一個整合小程式和公眾號的框架,個人開發小程式的時候只是簡單的看