zl程序教程

您现在的位置是:首页 >  后端

当前栏目

重学c#系列——盛派自定义异常源码分析(八)

c#源码异常 分析 系列 自定义
2023-09-14 08:59: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,是一个集成小程序和公众号的框架,个人开发小程序的时候只是简单的看了下。