C# 超高速高性能写日志

原理

使用列队先缓存到内存,独立线程从列队中使用log4net写到磁盘上。

日志写入列队

cs 复制代码
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();
    }
}

队列写入日志

cs 复制代码
/// <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);
    }
}

应用注意事项

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

注册示例

cs 复制代码
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();
    }
}

使用

调用FlashLogger的静态方法

cs 复制代码
FlashLogger.Debug("Debug");
FlashLogger.Debug("Debug", new Exception("testexception"));
FlashLogger.Info("Info");
FlashLogger.Fatal("Fatal");
FlashLogger.Error("Error");
FlashLogger.Warn("Warn", new Exception("testexception"));

参考链接

C# 超高速高性能写日志https://mp.weixin.qq.com/s?__biz=MzA4MTQyNDk4OA==&mid=2456958300&idx=2&sn=9c949a184d13e23f98e82417ac729421&chksm=895b198dcdf167e226da852887a430b409d74997d18ded44e20858db53fd841448fe11b93d96&mpshare=1&scene=1&srcid=1226pYPjHUCOMZmnq7FMGk86&sharer_shareinfo=ece2905c127436a9b5eae1003db433fd&sharer_shareinfo_first=0628f372cfaf61c028e18d6a8c508f50&exportkey=n_ChQIAhIQuA3e9zi0waukFDFCW1r%2ByhKfAgIE97dBBAEAAAAAAMFpCyDDdf4AAAAOpnltbLcz9gKNyK89dVj0uQI27swsOvYg368rEF3G9%2B72QxoOEAPAtRnBP0T8TmPbvTWYGbNG6ug7Za3ehBxruqhkk%2Bki%2BwUlj9NyGj7PzmMFO%2FJsij6UBGY3Vpgu3wKdrwe08W%2F0g4%2FAHVYW85it%2FEy104f1SNm0dtnV7sKSaTu8QbO%2FhqanrBe6n1u5q0B816ezwmh2m4RyMqxtUucoo0a2cOBvnIpfgO5pA%2F1KZ1naNd73ubHBMDQ2dU8E8SSQH2quniORgpyMqyOMK19GbsFh9Yh6xu1qsmX3iE5Ei%2F3G3JxxHvWaTR5uV5%2Fousn%2Fly4ctUTgA1mNvArHHHabEgtbFAVFrP5W&acctmode=0&pass_ticket=%2BHWz3yc2o8iZ6T2jkz0Pa27SKjP5Jc%2BuBoHv%2FE7tlB6avrF01Uh8JTBFsqDxKGA6&wx_header=0#rd

完整代码

cs 复制代码
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; }

    }
}

特此记录

anlog

2024年12月27日

相关推荐
小同志007 天前
Spring Boot ⽇志概述(简单了解)
java·java-ee·日志
进阶的猪8 天前
使用4G或Wifi推流到PC
日志
拾光Ծ8 天前
【Linux系统编程】线程池项目实战与基于策略模式的日志系统
linux·bash·线程池·策略模式·日志
jieyucx9 天前
Go 语言零基础入门:标准库 log 包完全教程
golang·日志·log
誰能久伴不乏12 天前
【Qt 架构实战】从零手写工业级 Qt 日志系统:底层拦截与架构原理解析
qt·架构·日志
JaydenAI14 天前
[MAF预定义的IChatClient中间件-01]LoggingChatClient——在LLM调用前后输出日志
ai·c#·agent·日志·agent管道·chatclient中间件
fengxin_rou18 天前
【MySQL 三大日志深度解析】:redo log、undo log、binlog 作用与两阶段提交原理
数据库·mysql·日志·redo log
不吃土豆的马铃薯19 天前
Spdlog 入门:日志记录器与日志槽基础详解
服务器·开发语言·c++·c·日志·spdlog
佛祖让我来巡山22 天前
测试环境日志爆内存?我用一个工具类搞定了双日志体系的智能打印
日志·日志打印·生产测试环境日志
观测云1 个月前
观测云日志转发至 Kafka 最佳实践
kafka·日志