c#编写⼀个轻量级的异步写⽇志的实⽤⼯具类(LogAsyncWriter)
⼀说到写⽇志,⼤家可能推荐⼀堆的开源⽇志框架,如:Log4Net、NLog,这些⽇志框架确实也不错,⽐较强⼤也⽐较灵活,但也正因为⼜强⼤⼜灵活,导致我们使⽤他们时需要引⽤⼀些DLL,同时还要学习各种⽤法及配置⽂件,这对于有些⼩⼯具、⼩程序、⼩⽹站来说,有点“杀鸡焉俺⽤⽜⼑”的感觉,⽽且如果对这些⽇志框架不了解,可能输出来的⽇志性
能或效果未毕是与⾃⼰所想的,鉴于这⼏个原因,我⾃⼰重复造轮⼦,编写了⼀个轻量级的异步写⽇志的实⽤⼯具类(LogAsyncWriter),这个类还是⽐较简单的,实现思路也很简单,就是把消息⽇志先⼊内存队列,然后由异步监听线程从队列中取出⽇志并批量输出到本地⽂件中,同时参照各⼤⽇志框架,对单个⽂件过⼤采取分割⽣成多个⽇志⽂件。
经测试发现性能⾮常不错,先看⽰例使⽤代码:(采取并发多线程同时写⼊1000000万条⽇志)
Task.Factory.StartNew(() =>
{
DateTime startTime = DateTime.Now;
int logCount = 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(new MethodInvoker(() =>
{
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属性来设置每条⽇志输出的格式内容
好了,介绍了使⽤功能及效果、性能,下⾯贴出源代码:
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;
using System.Text.RegularExpressions;
namespace Zuowj.Common
{
/// <summary>
/// ⽇志异步⽣成器
/// Author:Zuowenjun(www.zuowenjun)
/
// Date:2018-6-14
/// </summary>
public class LogAsyncWriter
{
public const string InfoLevel = "INFO";
public const string WarnLevel = "WARN";
public const string ErrorLevel = "ERROR";
private readonly ConcurrentQueue<string[]> logMsgQueue = new ConcurrentQueue<string[]>();
private readonly CancellationTokenSource cts = null;
private string lineLayoutRenderFormat = "[{0:yyyy-MM-dd HH:mm:ss}]\t{1}\t{2}\t{3}:{4},Trace:{5};Other1:{6},Other2:{7},Other3:{8}";
private long maxSizeBackup = 10485760L;//默认10MB
private string todayLogName = null;
private static readonly LogAsyncWriter instance = new LogAsyncWriter();
private LogAsyncWriter()
{
cts = new CancellationTokenSource();
ListenSaveLogAsync(cts.Token);
}
private void ListenSaveLogAsync(CancellationToken cancellationToken)
{
Task.Factory.StartNew(() =>
{
DateTime lastSaveLogTime = DateTime.Now;
while (!cancellationToken.IsCancellationRequested)//如果没有取消线程,则⼀直监听执⾏写LOG
{
if (logMsgQueue.Count >= 10 || (logMsgQueue.Count > 0 && (DateTime.Now - lastSaveLogTime).TotalSeconds > 30))//如是待写⽇志消息累计>=10条或上⼀次距离现在写⽇志时间超过30s则需要批量提交⽇志 {
List<string[]> logMsgList = new List<string[]>();
string[] logMsgItems = null;
while (logMsgList.Count < 10 && logMsgQueue.TryDequeue(out logMsgItems))
{
logMsgList.Add(logMsgItems);
}
WriteLog(logMsgList);
lastSaveLogTime = DateTime.Now;
}
else
{
SpinWait.SpinUntil(() => logMsgQueue.Count >= 10, 5000);//⾃旋等待直到⽇志队列有>=10的记录或超时5S后再进⼊下⼀轮的判断 }
}
}, cancellationToken);
}
private string GetLogFilePath()
{
string logFileDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Logs");
if (!Directory.Exists(logFileDir))
{
Directory.CreateDirectory(logFileDir);
}
string logDateStr = DateTime.Now.ToString("yyyyMMdd");
string logName = logDateStr;
if (!string.IsNullOrEmpty(todayLogName) && todayLogName.StartsWith(logName))
{
logName = todayLogName;
}
else
{
todayLogName = logName;
}
string logFilePath = 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 (string fileName in oldLogFiles)
{
File.SetAttributes(fileName, FileAttributes.Normal);
File.Delete(fileName);
}
}
else if (new FileInfo(logFilePath).Length > MaxSizeBackup)
{
Regex rgx = new Regex(@"^\d{8}-(?<fnum>\d{2})$");
int fnum = 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");
}
}
log4j2 异步写文件return logFilePath;
}
private void WriteLog(IEnumerable<string[]> logMsgs)
{
try
{
List<string> logMsgLines = new List<string>();
foreach (var logMsgItems in logMsgs)
{
var logMsgLineFields = (new object[] { DateTime.Now }).Concat(logMsgItems).ToArray();
string logMsgLineText = string.Format(LineLayoutRenderFormat, logMsgLineFields);
logMsgLines.Add(logMsgLineText);
}
string logFilePath = GetLogFilePath();
File.AppendAllLines(logFilePath, logMsgLines);
}
catch
{ }
}
public static LogAsyncWriter Default
{
get
{
return instance;
}
}
public string LineLayoutRenderFormat
{
get { return lineLayoutRenderFormat; }
set
{
if (string.IsNullOrWhiteSpace(value))
{
throw new ArgumentException("⽆效的LineLayoutRenderFormat属性值");
}
lineLayoutRenderFormat = value;
}
}
public long MaxSizeBackup
{
get { return maxSizeBackup; }
set
{
if (value <= 0)
{
throw new ArgumentException("⽆效的MaxSizeBackup属性值");
}
maxSizeBackup = value;
}
}
public void SaveLog(string logLevel, string msg, string source, string detailTrace = null, string other1 = null, string other2 = null, string other3 = null)
{
logMsgQueue.Enqueue(new[] { logLevel, Thread.CurrentThread.ManagedThreadId.ToString(), source, msg, detailTrace ?? string.Empty, other1 ?? string.Empty, other2 ?? string.Empty, other3 ?? string.Empty }); }
public void Info(string msg, string source, string detailTrace = null, string other1 = null, string other2 = null, string other3 = null)
{
SaveLog(InfoLevel, msg, source, detailTrace, other1, other2, other3);
}
public void Warn(string msg, string source, string detailTrace = null, string other1 = null, string other2 = null, string other3 = null)
{
SaveLog(WarnLevel, msg, source, detailTrace, other1, other2, other3);
}
public void Error(string msg, string source, string detailTrace = null, string other1 = null, string other2 = null, string other3 = null)
{
SaveLog(ErrorLevel, msg, source, detailTrace, other1, other2, other3);
}
public void Error(Exception ex, string source, string other1 = null, string other2 = null, string other3 = 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. log Level:⽇志消息级别⼀般有很多级别,但常⽤的只有3类,即:Info=普通⽇志消息,Warn=警告⽇志消息,Error=错误⽇志消息;
2.核⼼异步批量写⽇志的⽅法:(采⽤后台线程监听⽇志消息队列,当达到10条⽇志消息或写⽇志的时间间隔超过1分钟,则会批量提交1次⽇志,解决了普通的同步写⽇志⽅法造成写压⼒过⼤,且存在阻塞业务逻辑的情况)
3.采⽤单例模式,当第⼀次使⽤该类时,则会启动异步监听线程,当该类释放时(⼀般指应⽤进程关闭时,会发送通知线程取消监听,避免⼀切可能的线程驻留问题)
好了就介绍到这⾥,⼤家若想试⽤或想改造,可以直接复制上述代码,不⾜之处可以指出,谢谢!
以上就是c# 编写⼀个轻量级的异步写⽇志的实⽤⼯具类(LogAsyncWriter)的详细内容,更多关于c# 编写异步写⽇志⼯具类的资料请关注其它相关⽂章!
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论