c# 编写一个轻量级的异步写日志的实用工具类(LogAsyncWriter)
一说到写日志,大家可能推荐一堆的开源日志框架,如:Log4Net、NLog,这些日志框架确实也不错,比较强大也比较灵活,但也正因为又强大又灵活,导致我们使用他们时需要引用一些DLL,同时还要学习各种用法及配置文件,这对于有些小工具、小程序、小网站来说,有点“杀鸡焉俺用牛刀”的感觉,而且如果对这些日志框架不了解,可能输出来的日志性能或效果未毕是与自己所想的,鉴于这几个原因,我自己重复造轮子,编写了一个轻量级的异步写日志的实用工具类(LogAsyncWriter),这个类还是比较简单的,实现思路也很简单,就是把消息日志先入内存队列,然后由异步监听线程从队列中取出日志并批量输出到本地文件中,同时参照各大日志框架,对单个文件过大采取分割生成多个日志文件。
经测试发现性能非常不错,先看示例使用代码:(采取并发多线程同时写入1000000万条日志)
Task.Factory.StartNew(()=>
{
DateTimestartTime=DateTime.Now;
intlogCount=1000000;
Parallel.For(1,logCount,(i)=>
{
if(i%2==0)
{
LogAsyncWriter.Default.Error("测试并发写错误日志-"+i.ToString(),"TestClass.TestLog",i.ToString());
}
else
{
LogAsyncWriter.Default.Info("测试并发写普通日志-"+i.ToString(),"TestClass.TestLog",i.ToString());
}
});
this.Invoke(newMethodInvoker(()=>
{
MessageBox.Show(DateTime.Now.ToString()+","+logCount+"条日志写完了!,耗时:"+(DateTime.Now-startTime).TotalMilliseconds+"ms");
}));
});
MessageBox.Show(DateTime.Now.ToString()+",同步方法已结束");
}
执行效果如下图示:
因为采用异步,故方法先走到结尾,输出了同步的MsgBox,随后弹出的是100W日志输出到文件后的耗时MsgBox,从截图可以看出,不足1S(当然这里的1S不是真实的输出到本地方件,而是把所有的日志推到了Queue中而矣,但不影响不阻塞业务处理),而本地日志文件的大小达到了263MB(设置最大值的MaxSizeBackup,使其不滚动备份),由此看性能是不错的;
因为是异步延迟输出到本地日志文件,故大家在代码中任意地方,比如:循环中都可以使用它,不用担心会影响你的正常的业务逻辑的执行效率;
如果采取滚动备份(设置MaxSizeBackup为一个合理值,默认为10MB),则生成的日志文件形式如下:(超过最大容量则生成同名文件+2位序号),超过一个月则会自动清除
打开日志文件会看到所有的日志内容:
有些人可能要问,输出格式是固定的吗?能否自定义布局,告诉你是可以的,我考虑到每个人对日志输出的格式都有严格的要求,故可以通过设置:LineLayoutRenderFormat属性来设置每条日志输出的格式内容
好了,介绍了使用功能及效果、性能,下面贴出源代码:
usingSystem;
usingSystem.Collections.Concurrent;
usingSystem.Collections.Generic;
usingSystem.IO;
usingSystem.Linq;
usingSystem.Text;
usingSystem.Threading;
usingSystem.Threading.Tasks;
usingSystem.Text.RegularExpressions;
namespaceZuowj.Common
{
///
///日志异步生成器
///Author:Zuowenjun(http://www.zuowenjun.cn)
///Date:2018-6-14
///
publicclassLogAsyncWriter
{
publicconststringInfoLevel="INFO";
publicconststringWarnLevel="WARN";
publicconststringErrorLevel="ERROR";
privatereadonlyConcurrentQueuelogMsgQueue=newConcurrentQueue();
privatereadonlyCancellationTokenSourcects=null;
privatestringlineLayoutRenderFormat="[{0:yyyy-MM-ddHH:mm:ss}]\t{1}\t{2}\t{3}:{4},Trace:{5};Other1:{6},Other2:{7},Other3:{8}";
privatelongmaxSizeBackup=10485760L;//默认10MB
privatestringtodayLogName=null;
privatestaticreadonlyLogAsyncWriterinstance=newLogAsyncWriter();
privateLogAsyncWriter()
{
cts=newCancellationTokenSource();
ListenSaveLogAsync(cts.Token);
}
privatevoidListenSaveLogAsync(CancellationTokencancellationToken)
{
Task.Factory.StartNew(()=>
{
DateTimelastSaveLogTime=DateTime.Now;
while(!cancellationToken.IsCancellationRequested)//如果没有取消线程,则一直监听执行写LOG
{
if(logMsgQueue.Count>=10||(logMsgQueue.Count>0&&(DateTime.Now-lastSaveLogTime).TotalSeconds>30))//如是待写日志消息累计>=10条或上一次距离现在写日志时间超过30s则需要批量提交日志
{
ListlogMsgList=newList();
string[]logMsgItems=null;
while(logMsgList.Count<10&&logMsgQueue.TryDequeue(outlogMsgItems))
{
logMsgList.Add(logMsgItems);
}
WriteLog(logMsgList);
lastSaveLogTime=DateTime.Now;
}
else
{
SpinWait.SpinUntil(()=>logMsgQueue.Count>=10,5000);//自旋等待直到日志队列有>=10的记录或超时5S后再进入下一轮的判断
}
}
},cancellationToken);
}
privatestringGetLogFilePath()
{
stringlogFileDir=Path.Combine(AppDomain.CurrentDomain.BaseDirectory,"Logs");
if(!Directory.Exists(logFileDir))
{
Directory.CreateDirectory(logFileDir);
}
stringlogDateStr=DateTime.Now.ToString("yyyyMMdd");
stringlogName=logDateStr;
if(!string.IsNullOrEmpty(todayLogName)&&todayLogName.StartsWith(logName))
{
logName=todayLogName;
}
else
{
todayLogName=logName;
}
stringlogFilePath=Path.Combine(logFileDir,logName+".log");
if(File.Exists(logFilePath))
{
File.SetAttributes(logFilePath,FileAttributes.Normal);
if(File.GetLastWriteTime(logFilePath).Month!=DateTime.Today.Month)//30天滚动(删除旧的文件),防止日志文件过多
{
File.Delete(logFilePath);
string[]oldLogFiles=Directory.GetFiles(logFileDir,string.Format("{0}-##.log",logDateStr),SearchOption.TopDirectoryOnly);
foreach(stringfileNameinoldLogFiles)
{
File.SetAttributes(fileName,FileAttributes.Normal);
File.Delete(fileName);
}
}
elseif(newFileInfo(logFilePath).Length>MaxSizeBackup)
{
Regexrgx=newRegex(@"^\d{8}-(?\d{2})$");
intfnum=2;
if(rgx.IsMatch(logName))
{
fnum=int.Parse(rgx.Match(logName).Groups["fnum"].Value)+1;
}
logName=string.Format("{0}-{1:D2}",logDateStr,fnum);
todayLogName=logName;
logFilePath=Path.Combine(logFileDir,logName+".log");
}
}
returnlogFilePath;
}
privatevoidWriteLog(IEnumerablelogMsgs)
{
try
{
ListlogMsgLines=newList();
foreach(varlogMsgItemsinlogMsgs)
{
varlogMsgLineFields=(newobject[]{DateTime.Now}).Concat(logMsgItems).ToArray();
stringlogMsgLineText=string.Format(LineLayoutRenderFormat,logMsgLineFields);
logMsgLines.Add(logMsgLineText);
}
stringlogFilePath=GetLogFilePath();
File.AppendAllLines(logFilePath,logMsgLines);
}
catch
{}
}
publicstaticLogAsyncWriterDefault
{
get
{
returninstance;
}
}
publicstringLineLayoutRenderFormat
{
get{returnlineLayoutRenderFormat;}
set
{
if(string.IsNullOrWhiteSpace(value))
{
thrownewArgumentException("无效的LineLayoutRenderFormat属性值");
}
lineLayoutRenderFormat=value;
}
}
publiclongMaxSizeBackup
{
get{returnmaxSizeBackup;}
set
{
if(value<=0)
{
thrownewArgumentException("无效的MaxSizeBackup属性值");
}
maxSizeBackup=value;
}
}
publicvoidSaveLog(stringlogLevel,stringmsg,stringsource,stringdetailTrace=null,stringother1=null,stringother2=null,stringother3=null)
{
logMsgQueue.Enqueue(new[]{logLevel,Thread.CurrentThread.ManagedThreadId.ToString(),source,msg,detailTrace??string.Empty,other1??string.Empty,other2??string.Empty,other3??string.Empty});
}
publicvoidInfo(stringmsg,stringsource,stringdetailTrace=null,stringother1=null,stringother2=null,stringother3=null)
{
SaveLog(InfoLevel,msg,source,detailTrace,other1,other2,other3);
}
publicvoidWarn(stringmsg,stringsource,stringdetailTrace=null,stringother1=null,stringother2=null,stringother3=null)
{
SaveLog(WarnLevel,msg,source,detailTrace,other1,other2,other3);
}
publicvoidError(stringmsg,stringsource,stringdetailTrace=null,stringother1=null,stringother2=null,stringother3=null)
{
SaveLog(ErrorLevel,msg,source,detailTrace,other1,other2,other3);
}
publicvoidError(Exceptionex,stringsource,stringother1=null,stringother2=null,stringother3=null)
{
SaveLog(ErrorLevel,ex.Message,source,ex.StackTrace,other1,other2,other3);
}
~LogAsyncWriter()
{
cts.Cancel();
}
}
}
代码重点说明:
1.各种日志方法入参解释:
i.Msg:日志消息(一般是指简要消息)
ii.Source:日志产生源(一般是指该日志是由哪个位置产生的,可以定义为:类.方法名)
iii.detailTrace:日志详情(一般是指堆栈信息或日志更具体的信息)
iv.other1,other2,other3:备用日志字段,可根据实际情况记录相关信息,比如:入参、返参,执行耗时等;
v.logLevel:日志消息级别一般有很多级别,但常用的只有3类,即:Info=普通日志消息,Warn=警告日志消息,Error=错误日志消息;
2.核心异步批量写日志的方法:(采用后台线程监听日志消息队列,当达到10条日志消息或写日志的时间间隔超过1分钟,则会批量提交1次日志,解决了普通的同步写日志方法造成写压力过大,且存在阻塞业务逻辑的情况)
3.采用单例模式,当第一次使用该类时,则会启动异步监听线程,当该类释放时(一般指应用进程关闭时,会发送通知线程取消监听,避免一切可能的线程驻留问题)
好了就介绍到这里,大家若想试用或想改造,可以直接复制上述代码,不足之处可以指出,谢谢!
以上就是c#编写一个轻量级的异步写日志的实用工具类(LogAsyncWriter)的详细内容,更多关于c#编写异步写日志工具类的资料请关注毛票票其它相关文章!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。