1、需求
需求很简单,就是在C#开发中高速写日志。比如在高并发,高流量的地方需要写日志。我们知道程序在操作磁盘时是比较耗时的,所以我们把日志写到磁盘上会有一定的时间耗在上面,这些并不是我们想看到的。
2、解决方案
2.1、简单原理说明
使用列队先缓存到内存,然后我们一直有个线程再从列队中写到磁盘上,这样就可以高速高性能的写日志了。因为速度慢的地方我们分离出来了,也就是说程序在把日志扔给列队后,程序的日志部分就算完成了,后面操作磁盘耗时的部分程序是不需要关心的,由另一个线程操作。
俗话说,鱼和熊掌不可兼得,这样会有一个问题,就是如果日志已经到列队了这个时候程序崩溃或者电脑断电都会导致日志部分丢失,但是有些地方为了高性能的写日志,是否可以忽略一些情况,请各位根据情况而定。
2.2、示例图

3、关键代码部分
这里写日志的部分LZ选用了比较常用的,当然也可以选择其他的日志组件,比如nlog等等。
3.1、日志至列队部分
第一步我们首先需要把日志放到列队中,然后才能从列队中写到磁盘上。
EnqueueMessage( message,FlashLogLevel level,Exception ex = ((level == FlashLogLevel.Debug &&|| (level == FlashLogLevel.Error &&|| (level == FlashLogLevel.Fatal &&|| (level == FlashLogLevel.Info &&|| (level == FlashLogLevel.Warn &&= + DateTime.Now.ToString() + +== </span><span style="color: #008000;">//</span><span style="color: #008000;"> 通知线程往磁盘中写日志</span>
<span style="color: #000000;"> _mre.Set();
}
}
_log是log4net日志组件的ILog,其中包含了写日志,判断日志等级等功能,代码开始部分的if判断就是判断等级和现在的日志等级做对比,看是否需要写入列队,这样可以有效的提高日志的性能。
其中的_que是ConcurrentQueue列队。_mre是ManualResetEvent信号,ManualResetEvent是用来通知线程列队中有新的日志,可以从列队中写入磁盘了。当从列队中写完日志后,重新设置信号,在等待下次有新的日志到来。
3.2、列队到磁盘
从列队到磁盘我们需要有一个线程从列队写入磁盘,也就是说我们在程序启动时就要加载这个线程,比如asp.net中就要在global中的Application_Start中加载。
<div class="cnblogs_code">
= Thread(= </span><span style="color: #808080;">///</span> <span style="color: #808080;"><summary></span>
<span style="color: #808080;">///</span><span style="color: #008000;"> 从队列中写日志至磁盘
</span><span style="color: #808080;">///</span> <span style="color: #808080;"></summary></span>
<span style="color: #0000ff;">private</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> WriteLog()
{
</span><span style="color: #0000ff;">while</span> (<span style="color: #0000ff;">true</span><span style="color: #000000;">)
{
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 等待信号通知</span>
<span style="color: #000000;"> _mre.WaitOne();
FlashLogMessage msg;
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 判断是否有内容需要如磁盘 从列队中获取内容,并删除列队中的内容</span>
<span style="color: #0000ff;">while</span> (_que.Count > <span style="color: #800080;">0</span> && _que.TryDequeue(<span style="color: #0000ff;">out</span><span style="color: #000000;"> msg))
{
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 判断日志等级,然后写日志</span>
<span style="color: #0000ff;">switch</span><span style="color: #000000;"> (msg.Level)
{
</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> FlashLogLevel.Debug:
_log.Debug(msg.Message,msg.Exception);
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> FlashLogLevel.Info:
_log.Info(msg.Message,msg.Exception);
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> FlashLogLevel.Error:
_log.Error(msg.Message,msg.Exception);
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> FlashLogLevel.Warn:
_log.Warn(msg.Message,msg.Exception);
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> FlashLogLevel.Fatal:
_log.Fatal(msg.Message,msg.Exception);
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
}
}
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 重新设置信号</span>
<span style="color: #000000;"> _mre.Reset(); ?Thread.Sleep(1);
}
}
3.3、完整代码
<span style="color: #0000ff;">namespace<span style="color: #000000;"> Emrys.FlashLog
{
<span style="color: #0000ff;">public <span style="color: #0000ff;">sealed <span style="color: #0000ff;">class<span style="color: #000000;"> FlashLogger
{
<span style="color: #808080;">/// <span style="color: #808080;">
<span style="color: #808080;">///<span style="color: #008000;"> 记录消息Queue
<span style="color: #808080;">/// <span style="color: #808080;">
<span style="color: #0000ff;">private <span style="color: #0000ff;">readonly ConcurrentQueue<span style="color: #000000;"> _que;
</span><span style="color: #808080;">///</span> <span style="color: #808080;"><summary></span>
<span style="color: #808080;">///</span><span style="color: #008000;"> 信号
</span><span style="color: #808080;">///</span> <span style="color: #808080;"></summary></span>
<span style="color: #0000ff;">private</span> <span style="color: #0000ff;">readonly</span><span style="color: #000000;"> ManualResetEvent _mre;
</span><span style="color: #808080;">///</span> <span style="color: #808080;"><summary></span>
<span style="color: #808080;">///</span><span style="color: #008000;"> 日志
</span><span style="color: #808080;">///</span> <span style="color: #808080;"></summary></span>
<span style="color: #0000ff;">private</span> <span style="color: #0000ff;">readonly</span><span style="color: #000000;"> ILog _log;
</span><span style="color: #808080;">///</span> <span style="color: #808080;"><summary></span>
<span style="color: #808080;">///</span><span style="color: #008000;"> 日志
</span><span style="color: #808080;">///</span> <span style="color: #808080;"></summary></span>
<span style="color: #0000ff;">private</span> <span style="color: #0000ff;">static</span> FlashLogger _flashLog = <span style="color: #0000ff;">new</span><span style="color: #000000;"> FlashLogger();
</span><span style="color: #0000ff;">private</span><span style="color: #000000;"> FlashLogger()
{
</span><span style="color: #0000ff;">var</span> configFile = <span style="color: #0000ff;">new</span> FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,<span style="color: #800000;">"</span><span style="color: #800000;">log4net.config</span><span style="color: #800000;">"</span><span style="color: #000000;">));
</span><span style="color: #0000ff;">if</span> (!<span style="color: #000000;">configFile.Exists)
{
</span><span style="color: #0000ff;">throw</span> <span style="color: #0000ff;">new</span> Exception(<span style="color: #800000;">"</span><span style="color: #800000;">未配置log4net配置文件!</span><span style="color: #800000;">"</span><span style="color: #000000;">);
}
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 设置日志配置文件路径</span>
<span style="color: #000000;"> XmlConfigurator.Configure(configFile);
_que </span>= <span style="color: #0000ff;">new</span> ConcurrentQueue<FlashLogMessage><span style="color: #000000;">();
_mre </span>= <span style="color: #0000ff;">new</span> ManualResetEvent(<span style="color: #0000ff;">false</span><span style="color: #000000;">);
_log </span>=<span style="color: #000000;"> LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
}
</span><span style="color: #808080;">///</span> <span style="color: #808080;"><summary></span>
<span style="color: #808080;">///</span><span style="color: #008000;"> 实现单例
</span><span style="color: #808080;">///</span> <span style="color: #808080;"></summary></span>
<span style="color: #808080;">///</span> <span style="color: #808080;"><returns></returns></span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span><span style="color: #000000;"> FlashLogger Instance()
{
</span><span style="color: #0000ff;">return</span><span style="color: #000000;"> _flashLog;
}
</span><span style="color: #808080;">///</span> <span style="color: #808080;"><summary></span>
<span style="color: #808080;">///</span><span style="color: #008000;"> 另一个线程记录日志,只在程序初始化时调用一次
</span><span style="color: #808080;">///</span> <span style="color: #808080;"></summary></span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> Register()
{
Thread t </span>= <span style="color: #0000ff;">new</span> Thread(<span style="color: #0000ff;">new</span><span style="color: #000000;"> ThreadStart(WriteLog));
t.IsBackground </span>= <span style="color: #0000ff;">false</span><span style="color: #000000;">;
t.Start();
}
</span><span style="color: #808080;">///</span> <span style="color: #808080;"><summary></span>
<span style="color: #808080;">///</span><span style="color: #008000;"> 从队列中写日志至磁盘
</span><span style="color: #808080;">///</span> <span style="color: #808080;"></summary></span>
<span style="color: #0000ff;">private</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> WriteLog()
{
</span><span style="color: #0000ff;">while</span> (<span style="color: #0000ff;">true</span><span style="color: #000000;">)
{
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 等待信号通知</span>
<span style="color: #000000;"> _mre.WaitOne();
FlashLogMessage msg;
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 判断是否有内容需要如磁盘 从列队中获取内容,并删除列队中的内容</span>
<span style="color: #0000ff;">while</span> (_que.Count > <span style="color: #800080;">0</span> && _que.TryDequeue(<span style="color: #0000ff;">out</span><span style="color: #000000;"> msg))
{
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 判断日志等级,然后写日志</span>
<span style="color: #0000ff;">switch</span><span style="color: #000000;"> (msg.Level)
{
</span><span style="color: #0000ff;">case</span><span style="color: #000000;"> FlashLogLevel.Debug:
_log.Debug(msg.Message,msg.Exception);
</span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
}
}
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 重新设置信号</span>
<span style="color: #000000;"> _mre.Reset();
Thread.Sleep( <span style="color: #800080;">1<span style="color: #000000;">);
}
}
</span><span style="color: #808080;">///</span> <span style="color: #808080;"><summary></span>
<span style="color: #808080;">///</span><span style="color: #008000;"> 写日志
</span><span style="color: #808080;">///</span> <span style="color: #808080;"></summary></span>
<span style="color: #808080;">///</span> <span style="color: #808080;"><param name="message"></span><span style="color: #008000;">日志文本</span><span style="color: #808080;"></param></span>
<span style="color: #808080;">///</span> <span style="color: #808080;"><param name="level"></span><span style="color: #008000;">等级</span><span style="color: #808080;"></param></span>
<span style="color: #808080;">///</span> <span style="color: #808080;"><param name="ex"></span><span style="color: #008000;">Exception</span><span style="color: #808080;"></param></span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span> EnqueueMessage(<span style="color: #0000ff;">string</span> message,Exception </span>=<span style="color: #000000;"> ex
});
</span><span style="color: #008000;">//</span><span style="color: #008000;"> 通知线程往磁盘中写日志</span>
<span style="color: #000000;"> _mre.Set();
}
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> Debug(<span style="color: #0000ff;">string</span> msg,Exception ex = <span style="color: #0000ff;">null</span><span style="color: #000000;">)
{
Instance().EnqueueMessage(msg,FlashLogLevel.Debug,ex);
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> Error(<span style="color: #0000ff;">string</span> msg,FlashLogLevel.Error,ex);
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> Fatal(<span style="color: #0000ff;">string</span> msg,FlashLogLevel.Fatal,ex);
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> Info(<span style="color: #0000ff;">string</span> msg,FlashLogLevel.Info,ex);
}
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">static</span> <span style="color: #0000ff;">void</span> Warn(<span style="color: #0000ff;">string</span> msg,FlashLogLevel.Warn,ex);
}
}
</span><span style="color: #808080;">///</span> <span style="color: #808080;"><summary></span>
<span style="color: #808080;">///</span><span style="color: #008000;"> 日志等级
</span><span style="color: #808080;">///</span> <span style="color: #808080;"></summary></span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">enum</span><span style="color: #000000;"> FlashLogLevel
{
Debug,Info,Error,Warn,Fatal
}
</span><span style="color: #808080;">///</span> <span style="color: #808080;"><summary></span>
<span style="color: #808080;">///</span><span style="color: #008000;"> 日志内容
</span><span style="color: #808080;">///</span> <span style="color: #808080;"></summary></span>
<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">class</span><span style="color: #000000;"> FlashLogMessage
{
</span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">string</span> Message { <span style="color: #0000ff;">get</span>; <span style="color: #0000ff;">set</span><span style="color: #000000;">; }
</span><span style="color: #0000ff;">public</span> FlashLogLevel Level { <span style="color: #0000ff;">get</span>; <span style="color: #0000ff;">set</span><span style="color: #000000;">; }
</span><span style="color: #0000ff;">public</span> Exception Exception { <span style="color: #0000ff;">get</span>; <span style="color: #0000ff;">set</span><span style="color: #000000;">; }
}
}
4、性能对比和应用
4.1、性能对比
经过测试发现
使用原始的log4net写入日志100000条数据需要:19104毫秒。
同样数据使用列队方式只需要。

4.2、应用
4.2.1、需要在程序启动时注册,如asp.net 程序中在Global.asax中的Application_Start注册。
<em><span style="color: #ff0000;"><strong> FlashLogger.Instance().Register();</strong></span></em>
}
}</span></pre>
4.2.2、在需要写入日志的地方直接调用FlashLogger的静态方法即可。
FlashLogger.Debug(, Exception(, Exception());
5、代码开源
最后望对各位有所帮助,本文原创,欢迎拍砖和推荐。 ? (编辑:李大同)
【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!
|