前言
接着异常七后,因为以前看过盛派这块代码,正好重新整理一下。
正文
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,是一个集成小程序和公众号的框架,个人开发小程序的时候只是简单的看了下。