C# 超高速高性能写日志 代码开源
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 > && _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 > && _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();
}
} /// <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; } } }
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
最后望对各位有所帮助,本文原创,欢迎拍砖和推荐。
C# 超高速高性能写日志 代码开源的更多相关文章
- [转]C# 超高速高性能写日志 代码开源
1.需求 需求很简单,就是在C#开发中高速写日志.比如在高并发,高流量的地方需要写日志.我们知道程序在操作磁盘时是比较耗时的,所以我们把日志写到磁盘上会有一定的时间耗在上面,这些并不是我们想看到的 ...
- JAVA语言之怎样写出高性能的Java代码?
本文主要向大家介绍了JAVA语言之怎样写出高性能的 Java 代码?通过具体的内容向大家展示,希望对大家学习JAVA语言有所帮助. 在这篇文章中,我们将讨论几个有助于提升Java应用程序性能的方法.我 ...
- 收藏收藏:时隔一年,你关注的打造一个实用的TXT文本操作及日志框架,我们开源了,不再为程序写日志发愁(也支持.net core哦)
记得做这个框架是在2018年刚接触.net core的时候,那个时候为了能够专心的研究我开始不写博客了,但是学有所成并在公司运用了近一年的时间了,决定回来和各位分享我们所掌握的那星星点点的知识,希望可 ...
- 2.2 代码块--delphi 写日志模块
//2.2 代码块--写日志 //调用例句如:LogMsg('FTP上传线程终止',False,true); procedure LogMsg(AMsg: string; const blnIsErr ...
- How To Write In Sharepoint Log File 怎么对自定义的MOSS代码写日志
How To Write In Sharepoint Log File 怎么对自定义的MOSS代码写日志 Add Microsoft.Office.Server dll in your project ...
- 【编程练习】收集的一些c++代码片,算法排序,读文件,写日志,快速求积分等等
写日志: class LogFile { public: static LogFile &instance(); operator FILE *() const { return m_file ...
- 之前写的收集的一些c++代码片,算法排序,读文件,写日志,快速求积分等等
写日志: class LogFile { public: static LogFile &instance(); operator FILE *() const { return m_file ...
- 重复造轮子,编写一个轻量级的异步写日志的实用工具类(LogAsyncWriter)
一说到写日志,大家可能推荐一堆的开源日志框架,如:Log4Net.NLog,这些日志框架确实也不错,比较强大也比较灵活,但也正因为又强大又灵活,导致我们使用他们时需要引用一些DLL,同时还要学习各种用 ...
- .NET Core的日志[5]:利用TraceSource写日志
从微软推出第一个版本的.NET Framework的时候,就在“System.Diagnostics”命名空间中提供了Debug和Trace两个类帮助我们完成针对调试和跟踪信息的日志记录.在.NET ...
随机推荐
- 前端基本知识(三):JS的闭包理解
JS闭包的理解 一.变量的作用域 二.如何从外部读取局部变量 三.什么是闭包 四.深入理解闭包 五.闭包的用途 六.使用闭包注意情况 七.JavaScript的垃圾回收机制 八.一些思考题 一.变量作 ...
- 学习接水系统(java+thread线程)
(一)项目框架分析 对于学生并发接水项目,根据面向对象的思想,需要创建两个对象,即学生和水龙头. 接下来主要讲解不排队接水和排队接水两张情况. 项目的目录文件如下: (二)不排队接水 假设有四个学生小 ...
- TP框架 基础1
php框架 一.真实项目开发步骤: 多人同时开发项目,协作开发项目.分工合理.效率有提高(代码风格不一样.分工不好) 测试阶段 上线运行 对项目进行维护.修改.升级(单个人维护项目,十分困难,代码风格 ...
- wemall app商城源码中基于PHP的通用的树型类代码
wemall doraemon是Android客户端程序,服务端采用wemall微信商城,不对原商城做任何修改,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可随意定制修改.本文分享其中 ...
- 1753: [Usaco2005 qua]Who's in the Middle
1753: [Usaco2005 qua]Who's in the Middle Time Limit: 5 Sec Memory Limit: 64 MBSubmit: 290 Solved: ...
- sed 删除换行符
sed 删除换行符 sed ':label;N;s/\n/:/;b label' filename sed ':label;N;s/\n/:/;t label' filename 上面的两条命令可以实 ...
- Java数据类型及其转换&&经常用到的快捷键
数据类型 基本数据类型分类 (8种) byte .short. int. long. char. float. double .boolean 1个字节占8位 整数型byte 1字节 -128~1 ...
- Android学习探索之本地原生渲染 LaTeX数据公式
前言: 一直致力于为公司寻找更加高效的解决方案,作为一款K12在线教育App,功能中难免会有LaTeX数学公式的显示需求,这部分公司已经实现了此功能,只是个人觉得在体验和效率上还是不太好,今天来聊一下 ...
- Chapter 4. Working with Key/Value Pairs
Chapter4 working with key/value pairs key/values pairs键值对是Spark中非常常见的一种数据类型(type),RDD有时经常操作键值对数据类型.第 ...
- 混合拉普拉斯分布(LMM)推导及实现
作者:桂. 时间:2017-03-21 07:25:17 链接:http://www.cnblogs.com/xingshansi/p/6592599.html 声明:欢迎被转载,不过记得注明出处哦 ...