前言

1、需求

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

解决方案

2.1、简单原理说明

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

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

2.2、示例图

关键代码部分

这里写日志的部分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();
}
}

完整代码

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.1、性能对比

经过测试发现

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

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

应用

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"));

代码开源

前去 Git下载

本文为转载文章,原文地址:http://www.cnblogs.com/emrys5/p/flashlog.html

C# Log4net记录日志的更多相关文章

  1. [转]C#使用Log4Net记录日志

    第一步:下载Log4Net 下载地址:http://logging.apache.org/log4net/download_log4net.cgi 把下载的  log4net-1.2.11-bin-n ...

  2. C# 使用 log4net 记录日志

    Ø  前言 在一般的开发应用中,都会涉及到日志记录,用于排查错误 或 记录程序运行时的日志信息.log4net 库是 Apache log4j 框架在 Microsoft .NET 平台的实现,是一个 ...

  3. ASP.NET Core使用log4net记录日志

    .NET常用的日志组件有NLog.Log4net等,.NET CORE下微软也自带了日志组件,到目前为止还没用过,而我本人常用的是log4net,下面简单讲讲.NET CORE下怎么使用log4net ...

  4. 用log4net记录日志信息

    在.net中用log4net记录日志信息,已经是很平常的事情了. log4net下载:http://logging.apache.org/log4net/download_log4net.cgi 百度 ...

  5. 将WebService部署到 SharePoint 2010 gac 缓存中,并用Log4Net记录日志到数据库

    最近做了一个sharePoint项目,需要实现的功能是,第三方网站访问我们sharePoint中的数据,通过Webservice方式实现文件的上传和下载. 于是代码工作完成了之后,本地调试没什么问题, ...

  6. Windows服务使用log4net记录日志

    该文章是系列文章 基于.NetCore和ABP框架如何让Windows服务执行Quartz定时作业 的其中一篇. 比较流行的日志组件有以下四种,Topshelf都有相应的组件提供 log4net NL ...

  7. 使用Log4net记录日志(非常重要)

    使用Log4net记录日志   首先说说为什么要进行日志记录.在一个完整的程序系统里面,日志系统是一个非常重要的功能组成部分.它可以记录下系统所产生的所有行为,并按照某种规范表达出来.我们可以使用日志 ...

  8. Log4Net记录日志的使用

    Log4net 基本样式: <log4net> <appender name="LogFileAppender" type="log4net.Appen ...

  9. Log4net 记录日志

    配置文件 备注,需要放到根目录下面 <?xml version="1.0" encoding="utf-8"?> <configuration ...

  10. 使用 Log4Net 记录日志

    第一步:下载Log4Net 下载地址:http://logging.apache.org/log4net/download_log4net.cgi 把下载的  log4net-1.2.11-bin-n ...

随机推荐

  1. Carbondata源码系列(二)文件格式详解

    在上一章当中,写了文件的生成过程.这一章主要讲解文件格式(V3版本)的具体细节. 1.字典文件格式详解 字典文件的作用是在存储的时候将字符串等类型转换为int类型,好处主要有两点: 1.减少存储占用空 ...

  2. Python将纳入高考?

    最近,"Python将纳入高考"的消息,狠狠地刷了朋友圈. 尽管这则消息目前还未得到官方的确认,但人们对于Python的火热关注度,还是引来众程序员热议. 虽然小编资历尚浅,但还是 ...

  3. HDU 1394 逆序数 线段树单点跟新 | 暴力

    Minimum Inversion Number Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java ...

  4. CCF-201509-2-日期计算

    问题描述 试题编号: 201509-2 试题名称: 日期计算 时间限制: 1.0s 内存限制: 256.0MB 问题描述: 问题描述 给定一个年份y和一个整数d,问这一年的第d天是几月几日? 注意闰年 ...

  5. centos 自带mysql卸载时出现无法卸载情况

    [dianyi@localhost ~]$ rpm -qa|grep mysql mysql-libs-5.1.52-1.el6_0.1.x86_64 [dianyi@localhost ~]$ rp ...

  6. Android异步消息机制

    Android中的异步消息机制分为四个部分:Message.Handler.MessageQueue和Looper. 其中,Message是线程之间传递的消息,其what.arg1.arg2字段可以携 ...

  7. Python 学习(1) 简单的小爬虫

    最近抽空学了两天的Python,基础知识都看完了,正好想申请个联通日租卡,就花了2小时写了个小爬虫,爬一下联通日租卡的申请页面,看有没有好记一点的手机号~   人工挑眼都挑花了. 用的IDE是PyCh ...

  8. jenkins+github持续集成中的坑

    1.前言 刚开始开发自己的独立博客的时候,每次发布都要手动打包,上传服务器,杀tomcat进程,重启,来回这么重复性工作,很快就有点不耐烦了.如果能自动化的东西,就绝不要手动了,所以自己搭建了个持续集 ...

  9. Android Static分析

    1.static的变量或者方法会放在内存的一个方法区内 2.static变量会在该变量所属的类从内存中释放掉之后. 才会释放掉而类释放的条件是非常苛刻的. 必需要该类相应的全部对象被释放掉. 同一时候 ...

  10. log4j配置文件详解(转)

    Log4J的配置文件(Configuration File)就是用来设置记录器的级别.存放器和布局的,它可接key=value格式的设置或xml格式的设置信息.通过配置,可以创建出Log4J的运行环境 ...