需求很简单,就是在 C# 开发中高速写日志。比如在高并发,高流量的地方需要写日志。我们知道程序在操作磁盘时是比较耗时的,所以我们把日志写到磁盘上会有一定的时间耗在上面,这些并不是我们想看到的。
使用列队先缓存到内存,然后我们一直有个线程再从列队中写到磁盘上,这样就可以高速高性能的写日志了。因为速度慢的地方我们分离出来了,也就是说程序在把日志扔给列队后,程序的日志部分就算完成了,后面操作磁盘耗时的部分程序是不需要关心的,由另一个线程操作。
俗话说,鱼和熊掌不可兼得,这样会有一个问题,就是如果日志已经到列队了这个时候程序崩溃或者电脑断电都会导致日志部分丢失,但是有些地方为了高性能的写日志,是否可以忽略一些情况,请各位根据情况而定。
这里写日志的部分 LZ 选用了比较常用的 log4net,当然也可以选择其他的日志组件,比如 nlog 等等。
第一步我们首先需要把日志放到列队中,然后才能从列队中写到磁盘上。
- public voidEnqueueMessage(stringmessage, FlashLogLevel level, Exception ex =null)
- {
- if((level == FlashLogLevel.Debug && _log.IsDebugEnabled)
- || (level == FlashLogLevel.Error && _log.IsErrorEnabled)
- || (level == FlashLogLevel.Fatal && _log.IsFatalEnabled)
- || (level == FlashLogLevel.Info && _log.IsInfoEnabled)
- || (level == FlashLogLevel.Warn && _log.IsWarnEnabled))
- {
- _que.Enqueue(new FlashLogMessage
- {
- Message ="["+ DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff") +"]\r\n"+ message,
- Level = level,
- Exception = ex
- });
- // 通知线程往磁盘中写日志
- _mre.Set();
- }
- }
_log 是 log4net 日志组件的 ILog,其中包含了写日志,判断日志等级等功能,代码开始部分的 if 判断就是判断等级和现在的日志等级做对比,看是否需要写入列队,这样可以有效的提高日志的性能。
其中的_que 是 ConcurrentQueue 列队。_mre 是 ManualResetEvent 信号,ManualResetEvent 是用来通知线程列队中有新的日志,可以从列队中写入磁盘了。当从列队中写完日志后,重新设置信号,在等待下次有新的日志到来。
从列队到磁盘我们需要有一个线程从列队写入磁盘,也就是说我们在程序启动时就要加载这个线程,比如 asp.net 中就要在 global 中的 Application_Start 中加载。
- /// <summary>
- /// 另一个线程记录日志,只在程序初始化时调用一次
- /// </summary>
- public void Register()
- {
- Thread t =newThread(new ThreadStart(WriteLog));
- t.IsBackground =false;
- t.Start();
- }
- /// <summary>
- /// 从队列中写日志至磁盘
- /// </summary>
- private void WriteLog()
- {
- while(true)
- {
- // 等待信号通知
- _mre.WaitOne();
- // 判断是否有内容需要如磁盘
- while(_que.Count >0)
- {
- FlashLogMessage msg;
- if(_que.TryDequeue(outmsg))// 从列队中获取内容,并删除列队中的内容
- {
- // 判断日志等级,然后写日志
- switch (msg.Level)
- {
- case FlashLogLevel.Debug:
- _log.Debug(msg.Message, msg.Exception);
- break;
- case FlashLogLevel.Info:
- _log.Info(msg.Message, msg.Exception);
- break;
- case FlashLogLevel.Error:
- _log.Error(msg.Message, msg.Exception);
- break;
- case FlashLogLevel.Warn:
- _log.Warn(msg.Message, msg.Exception);
- break;
- case FlashLogLevel.Fatal:
- _log.Fatal(msg.Message, msg.Exception);
- break;
- }
- }
- }
- // 重新设置信号
- _mre.Reset();
- }
- }
View Code
- using log4net;
- using log4net.Config;
- using System;
- using System.Collections.Concurrent;
- using System.IO;
- using System.Threading;
- namespace Emrys.FlashLog
- {
- public sealed class FlashLogger
- {
- /// <summary>
- /// 记录消息Queue
- /// </summary>
- private readonlyConcurrentQueue _que;
- /// <summary>
- /// 信号
- /// </summary>
- private readonly ManualResetEvent _mre;
- /// <summary>
- /// 日志
- /// </summary>
- private readonly ILog _log;
- /// <summary>
- /// 日志
- /// </summary>
- private staticFlashLogger _flashLog =new FlashLogger();
- private FlashLogger()
- {
- // 设置日志配置文件路径XmlConfigurator.Configure(newFileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"log4net.config")));
- _que =newConcurrentQueue();
- _mre =newManualResetEvent(false);
- _log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
- }
- /// <summary>
- /// 实现单例
- /// </summary>
- /// <returns></returns>
- public static FlashLogger Instance()
- {
- return _flashLog;
- }
- /// <summary>
- /// 另一个线程记录日志,只在程序初始化时调用一次
- /// </summary>
- public void Register()
- {
- Thread t =newThread(new ThreadStart(WriteLog));
- t.IsBackground =false;
- t.Start();
- }
- /// <summary>
- /// 从队列中写日志至磁盘
- /// </summary>
- private void WriteLog()
- {
- while(true)
- {
- // 等待信号通知
- _mre.WaitOne();
- // 判断是否有内容需要如磁盘
- while(_que.Count >0)
- {
- FlashLogMessage msg;
- if(_que.TryDequeue(outmsg))// 从列队中获取内容,并删除列队中的内容
- {
- // 判断日志等级,然后写日志
- switch (msg.Level)
- {
- case FlashLogLevel.Debug:
- _log.Debug(msg.Message, msg.Exception);
- break;
- case FlashLogLevel.Info:
- _log.Info(msg.Message, msg.Exception);
- break;
- case FlashLogLevel.Error:
- _log.Error(msg.Message, msg.Exception);
- break;
- case FlashLogLevel.Warn:
- _log.Warn(msg.Message, msg.Exception);
- break;
- case FlashLogLevel.Fatal:
- _log.Fatal(msg.Message, msg.Exception);
- break;
- }
- }
- }
- // 重新设置信号
- _mre.Reset();
- }
- }
- /// <summary>
- /// 写日志
- /// </summary>
- /// <param name="message">日志文本</param>
- /// <param name="level">等级</param>
- /// <param name="ex">Exception</param>
- public voidEnqueueMessage(stringmessage, FlashLogLevel level, Exception ex =null)
- {
- if((level == FlashLogLevel.Debug && _log.IsDebugEnabled)
- || (level == FlashLogLevel.Error && _log.IsErrorEnabled)
- || (level == FlashLogLevel.Fatal && _log.IsFatalEnabled)
- || (level == FlashLogLevel.Info && _log.IsInfoEnabled)
- || (level == FlashLogLevel.Warn && _log.IsWarnEnabled))
- {
- _que.Enqueue(new FlashLogMessage
- {
- Message ="["+ DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff") +"]\r\n"+ message,
- Level = level,
- Exception = ex
- });
- // 通知线程往磁盘中写日志
- _mre.Set();
- }
- }
- public static voidDebug(stringmsg, Exception ex =null)
- {
- Instance().EnqueueMessage(msg, FlashLogLevel.Debug, ex);
- }
- public static voidError(stringmsg, Exception ex =null)
- {
- Instance().EnqueueMessage(msg, FlashLogLevel.Error, ex);
- }
- public static voidFatal(stringmsg, Exception ex =null)
- {
- Instance().EnqueueMessage(msg, FlashLogLevel.Fatal, ex);
- }
- public static voidInfo(stringmsg, Exception ex =null)
- {
- Instance().EnqueueMessage(msg, FlashLogLevel.Info, ex);
- }
- public static voidWarn(stringmsg, Exception ex =null)
- {
- Instance().EnqueueMessage(msg, FlashLogLevel.Warn, ex);
- }
- }
- /// <summary>
- /// 日志等级
- /// </summary>
- public enum FlashLogLevel
- {
- Debug,
- Info,
- Error,
- Warn,
- Fatal
- }
- /// <summary>
- /// 日志内容
- /// </summary>
- public class FlashLogMessage
- {
- public stringMessage {get;set; }
- publicFlashLogLevel Level {get;set; }
- publicException Exception {get;set; }
- }
- }
经过测试发现
使用原始的 log4net 写入日志 100000 条数据需要:19104 毫秒。
同样数据使用列队方式只需要 251 毫秒。
- public class MvcApplication : System.web.HttpApplication
- {
- protected voidApplication_Start()
- {
- AreaRegistration.RegisterAllAreas();
- FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
- RouteConfig.RegisterRoutes(RouteTable.Routes);
- BundleConfig.RegisterBundles(BundleTable.Bundles); FlashLogger.Instance().Register();}
- }
- FlashLogger.Debug("Debug");
- FlashLogger.Debug("Debug",newException("testexception"));
- FlashLogger.Info("Info");
- FlashLogger.Fatal("Fatal");
- FlashLogger.Error("Error");
- FlashLogger.Warn("Warn",newException("testexception"));
https://github.com/Emrys5/Emrys.FlashLog
最后望对各位有所帮助,本文原创,欢迎拍砖和推荐。
来源: http://www.cnblogs.com/emrys5/p/flashlog.html