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

夢裡小探花發表於2020-08-24

前言

接著異常七後,因為以前看過盛派這塊程式碼,正好重新整理一下。

正文

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類; 然而在實踐中,這並沒有被發現增加顯著價值。

好的,那麼來看下其例項方法。

/// <summary>
/// BaseException
/// </summary>
/// <param name="message">異常訊息</param>
/// <param name="inner">內部異常資訊</param>
/// <param name="logged">是否已經使用WeixinTrace記錄日誌,如果沒有,BaseException會進行概要記錄</param>
public BaseException(string message, Exception inner, bool logged = false)
	: base(message, inner)
{
	if (!logged)
	{
		SenparcTrace.BaseExceptionLog(this);
	}
}

那麼看下BaseExceptionLog幹了什麼吧。

/// <summary>
/// BaseException 日誌
/// </summary>
/// <param name="ex"></param>
public static void BaseExceptionLog(Exception ex)
{
	BaseExceptionLog(new BaseException(ex.Message, ex, false));
}

/// <summary>
/// BaseException 日誌
/// </summary>
/// <param name="ex"></param>
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委託做啥呢?這個好像是我們傳進去的吧,找到我們穿進去的值。

/// <summary>
/// 結束日誌記錄
/// </summary>
protected static Action<SenparcTraceItem> _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:

/// <summary>
/// 獲取完整單條日誌的字串資訊
/// </summary>
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列印到了什麼地方:

/// <summary>
/// 佇列執行邏輯
/// </summary>
protected static Action<string> _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

看下它的例項化:

/// <summary>
/// WeixinException
/// </summary>
/// <param name="message">異常訊息</param>
/// <param name="inner">內部異常資訊</param>
/// <param name="logged">是否已經使用WeixinTrace記錄日誌,如果沒有,WeixinException會進行概要記錄</param>
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

實際上不出所料:

/// <summary>
/// WeixinException 日誌
/// </summary>
/// <param name="ex"></param>
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,是一個整合小程式和公眾號的框架,個人開發小程式的時候只是簡單的看了下。

相關文章