C# 超高速高性能写日志 代码开源

来源:互联网 发布:上海数据恢复中心 编辑:程序博客网 时间:2024/05/19 07:07
1、需求

需求很简单,就是在C#开发中高速写日志。比如在高并发,高流量的地方需要写日志。我们知道程序在操作磁盘时是比较耗时的,所以我们把日志写到磁盘上会有一定的时间耗在上面,这些并不是我们想看到的。

 

2、解决方案

2.1、简单原理说明

使用列队先缓存到内存,然后我们一直有个线程再从列队中写到磁盘上,这样就可以高速高性能的写日志了。因为速度慢的地方我们分离出来了,也就是说程序在把日志扔给列队后,程序的日志部分就算完成了,后面操作磁盘耗时的部分程序是不需要关心的,由另一个线程操作。

俗话说,鱼和熊掌不可兼得,这样会有一个问题,就是如果日志已经到列队了这个时候程序崩溃或者电脑断电都会导致日志部分丢失,但是有些地方为了高性能的写日志,是否可以忽略一些情况,请各位根据情况而定。

 

2.2、示例图

 

3、关键代码部分

这里写日志的部分LZ选用了比较常用的log4net,当然也可以选择其他的日志组件,比如nlog等等。

3.1、日志至列队部分

第一步我们首先需要把日志放到列队中,然后才能从列队中写到磁盘上。

        public void EnqueueMessage(string message, 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是用来通知线程列队中有新的日志,可以从列队中写入磁盘了。当从列队中写完日志后,重新设置信号,在等待下次有新的日志到来。

 

3.2、列队到磁盘

从列队到磁盘我们需要有一个线程从列队写入磁盘,也就是说我们在程序启动时就要加载这个线程,比如asp.net中就要在global中的Application_Start中加载。

 

       /// <summary>        /// 另一个线程记录日志,只在程序初始化时调用一次        /// </summary>        public void Register()        {            Thread t = new Thread(new ThreadStart(WriteLog));            t.IsBackground = false;            t.Start();        }        /// <summary>        /// 从队列中写日志至磁盘        /// </summary>        private void WriteLog()        {            while (true)            {                // 等待信号通知                _mre.WaitOne();                FlashLogMessage msg;                // 判断是否有内容需要如磁盘 从列队中获取内容,并删除列队中的内容                while (_que.Count > 0 && _que.TryDequeue(out msg))                {                    // 判断日志等级,然后写日志                    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();
          Thread.Sleep(1); } }

 

3.3、完整代码

using log4net;using log4net.Config;using System;using System.Collections.Concurrent;using System.Collections.Generic;using System.IO;using System.Linq;using System.Text;using System.Threading;using System.Threading.Tasks;namespace Emrys.FlashLog{    public sealed class FlashLogger    {        /// <summary>        /// 记录消息Queue        /// </summary>        private readonly ConcurrentQueue<FlashLogMessage> _que;        /// <summary>        /// 信号        /// </summary>        private readonly ManualResetEvent _mre;        /// <summary>        /// 日志        /// </summary>        private readonly ILog _log;        /// <summary>        /// 日志        /// </summary>        private static FlashLogger _flashLog = new FlashLogger();        private FlashLogger()        {            var configFile = new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "log4net.config"));            if (!configFile.Exists)            {                throw new Exception("未配置log4net配置文件!");            }            // 设置日志配置文件路径            XmlConfigurator.Configure(configFile);            _que = new ConcurrentQueue<FlashLogMessage>();            _mre = new ManualResetEvent(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 = new Thread(new ThreadStart(WriteLog));            t.IsBackground = false;            t.Start();        }        /// <summary>        /// 从队列中写日志至磁盘        /// </summary>        private void WriteLog()        {            while (true)            {                // 等待信号通知                _mre.WaitOne();                FlashLogMessage msg;                // 判断是否有内容需要如磁盘 从列队中获取内容,并删除列队中的内容                while (_que.Count > 0 && _que.TryDequeue(out msg))                {                    // 判断日志等级,然后写日志                    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();                Thread.Sleep(1);            }        }        /// <summary>        /// 写日志        /// </summary>        /// <param name="message">日志文本</param>        /// <param name="level">等级</param>        /// <param name="ex">Exception</param>        public void EnqueueMessage(string message, 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 void Debug(string msg, Exception ex = null)        {            Instance().EnqueueMessage(msg, FlashLogLevel.Debug, ex);        }        public static void Error(string msg, Exception ex = null)        {            Instance().EnqueueMessage(msg, FlashLogLevel.Error, ex);        }        public static void Fatal(string msg, Exception ex = null)        {            Instance().EnqueueMessage(msg, FlashLogLevel.Fatal, ex);        }        public static void Info(string msg, Exception ex = null)        {            Instance().EnqueueMessage(msg, FlashLogLevel.Info, ex);        }        public static void Warn(string msg, 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 string Message { get; set; }        public FlashLogLevel Level { get; set; }        public Exception Exception { get; set; }    }}
View Code

 

 

4、性能对比和应用

4.1、性能对比

经过测试发现

使用原始的log4net写入日志100000条数据需要:19104毫秒。

同样数据使用列队方式只需要251毫秒

 

 

 

4.2、应用

4.2.1、需要在程序启动时注册,如asp.net 程序中在Global.asax中的Application_Start注册。

    public class MvcApplication : System.Web.HttpApplication    {        protected void Application_Start()        {            AreaRegistration.RegisterAllAreas();            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);            RouteConfig.RegisterRoutes(RouteTable.Routes);            BundleConfig.RegisterBundles(BundleTable.Bundles);            FlashLogger.Instance().Register();        }    }

 

4.2.2、在需要写入日志的地方直接调用FlashLogger的静态方法即可。

            FlashLogger.Debug("Debug");            FlashLogger.Debug("Debug", new Exception("testexception"));            FlashLogger.Info("Info");            FlashLogger.Fatal("Fatal");            FlashLogger.Error("Error");            FlashLogger.Warn("Warn", new Exception("testexception"));

 

5、代码开源

https://github.com/Emrys5/Emrys.FlashLog

 

最后望对各位有所帮助,本文原创,欢迎拍砖和推荐。  

 

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 快递单号一直查不到物流信息怎么办 买家未收到货就发起仅退款怎么办 拼多多收货码短信没了怎么办 淘宝运费险快递单号填错怎么办 如果淘宝小二判定退款不同意怎么办 商品退回卖家电话关机不收货怎么办 咸鱼等待卖家提供凭证超时怎么办 闲鱼等待卖家上传凭证超时怎么办 集运仓说有违禁品不能入库怎么办 转运停止寄到转运仓了怎么办 为什么阿里免费开店会被限制怎么办 e盘里的文件已经更改或移动怎么办 决斗之城忘了在那个服务器怎么办 ipad需要激活锁忘了id怎么办 小米非系统储存空间满了怎么办的 学生打暑假工被代理拖欠工资怎么办 华为畅享7plus进水了怎么办 华为荣耀6plus喇叭坏了怎么办 华为荣耀7x听筒声音小怎么办 华为智能手表开不了机了怎么办 华为手机重启忘记解锁密码怎么办 华为荣耀8手机开不开机怎么办 苹果5c手机开机密码忘了怎么办 魅蓝note6打王者掉帧怎么办 王者荣耀6月26日活动怎么办 荣耀v9玩刺激战场掉帧怎么办 华为荣耀v9进水开不开机怎么办 魅蓝2玩游戏超卡怎么办 华为手机突然黑屏开不了机怎么办 三星a9开机混合密码忘了怎么办 三星a9锁屏密码忘了怎么办 华为6x手机电源键失灵怎么办 华为手机更换主题后图标不变怎么办 手机屏幕上的拨打电话键没了怎么办 小米手机电用完了充不进去怎么办 华为微信分身版本过低怎么办 手机连接不上4g网络怎么办办 手机通话时老返回锁屏怎么办 小米手机指纹解锁按键不灵了怎么办 小米手机4s黑屏开不了机怎么办 手机老年机突然黑屏开不了机怎么办