昨天打算把我以前写的一个C#写日志工具类放到GitHub上,却发现了一个BUG,当然,已经修复了。
然后写Demo对比了NLog和log4net,发现我这个LogUtil比它们性能低了不止一个数量级(后来发现是通过共用Mutex修复BUG导致的)。工作多年,平时都是用别人写的库,自己写的很少。因为当初自己没有时间研究log4net或NLog,并且写个简单的日志工具类自己也有能力实现,所以就自己写了LogUtil自己用。修修改改了很多次了,居然还是有BUG。因为用了多线程和锁,导致BUG很隐蔽,而且性能比较差(后来发现是通过共用Mutex修复BUG导致的)。代码写的很挫,逻辑复杂,更容易出BUG。用NLog或log4net它不香吗?但又心有不甘,而且对于自己写的一些小的程序,可能第三方日志类库的dll比自己的程序都大,所以也有必要自己写一个,以便平时写各种Demo用。
之前写的很挫,逻辑很复杂的日志工具类:https://www.cnblogs.com/s0611163/p/4023859.html
日志类型LogType类:
using System; System.Collections.Generic; System.linq; System.Text; System.Threading.Tasks;namespace Utils{ /// <summary> /// 日志类型 </summary> public enum LogType { DeBUG,Info,Error }}VIEw Code
当前日志写入流LogStream类:
System.IO; Utils{ internal class LogStream { public fileStream CurrentfileStream { get; set; } public StreamWriter CurrentStreamWriter { int CurrentArchiveIndex { long CurrentfileSize { string CurrentDateStr { string CurrentLogfilePath { string CurrentLogfileDir { ; } }}VIEw Code
LogWriter类:
System.Collections.Concurrent; System.Reflection; System.Text.RegularExpressions; System.Threading; LogWriter { #region 字段属性 private LogType _logType; private string _basePath; int _fileSize = 10 * 1024 * 1024; //日志分隔文件大小 private LogStream _currentStream = new LogStream(); string _dateFormat = "yyyyMMdd"; 日志文件名日期格式化 string _rootFolder = Log日志文件夹名称 object _lockWriter = new object(); #endregion #region LogWriter public LogWriter(LogType logType) { _logType = logType; Init(); } #region Init <summary> 初始化 </summary> voID Init() { 初始化 _basePath InitBasePath(); 创建目录 CreateLogDir(); 更新日志写入流 UpdateCurrentStream(); } #region 初始化 _basePath 初始化 _basePath InitBasePath() { UriBuilder uri = UriBuilder(Assembly.GetExecutingAssembly().CodeBase); _basePath = Path.GetDirectoryname(Uri.UnescapeDataString(uri.Path)); } #region 初始化 _currentArchiveIndex 初始化 _currentArchiveIndex InitCurrentArchiveIndex() { Regex regex = new Regex(_currentStream.CurrentDateStr + _*(\d*).txt"); string[] fileArr = Directory.Getfiles(_currentStream.CurrentLogfileDir,_currentStream.CurrentDateStr + *foreach (string file in fileArr) { Match match = regex.Match(file); if (match.Success) { string str = match.Groups[1].Value; if (!.IsNullOrWhiteSpace(str)) { int temp = Convert.ToInt32(str); if (temp > _currentStream.CurrentArchiveIndex) { _currentStream.CurrentArchiveIndex = temp; } } else { _currentStream.CurrentArchiveIndex = 0; } } } } #region 初始化 _currentfileSize 初始化 _currentfileSize InitCurrentfileSize() { fileInfo fileInfo = fileInfo(_currentStream.CurrentLogfilePath); _currentStream.CurrentfileSize = fileInfo.Length; } #region CreateLogDir() 创建日志目录 CreateLogDir() { string logDir = Path.Combine(_basePath,_rootFolder + \" + _logType.ToString()); Directory.Exists(logDir)) { Directory.CreateDirectory(logDir); } } #region CreateStream 创建日志写入流 CreateStream() { _currentStream.CurrentfileStream = fileStream(_currentStream.CurrentLogfilePath,fileMode.Append,fileAccess.Write,fileShare.ReaDWrite); _currentStream.CurrentStreamWriter = StreamWriter(_currentStream.CurrentfileStream,EnCoding.UTF8); } #region CloseStream 关闭日志写入流 CloseStream() { if (_currentStream.CurrentStreamWriter != null) { _currentStream.CurrentStreamWriter.Close(); } if (_currentStream.CurrentfileStream != ) { _currentStream.CurrentfileStream.Close(); } } #region 拼接日志内容 拼接日志内容 static string CreateLogString(LogType logType, log) { return string.Format(@"{0} {1} {2}",DateTime.Now.ToString(yyyy-MM-dd HH:mm:ss.fff"),([" + logType.ToString() + ]").padright(7,' '),log); } #region 写文件 写文件 voID Writefile(try { lock (_lockWriter) { 判断是否更新Stream string dateStr = DateTime.Now.ToString(_dateFormat); if (_currentStream.CurrentDateStr != dateStr) { _currentStream.CurrentDateStr = dateStr; UpdateCurrentStream(); } 判断是否创建Archive int byteCount = EnCoding.UTF8.GetByteCount(log); _currentStream.CurrentfileSize += byteCount; if (_currentStream.CurrentfileSize >= _fileSize) { _currentStream.CurrentfileSize = ; CreateArchive(); } 日志内容写入文件 _currentStream.CurrentStreamWriter.Writeline(log); _currentStream.CurrentStreamWriter.Flush(); } } catch (Exception ex) { Console.Writeline(ex.Message + \r\n ex.StackTrace); } } #region CreateArchive 创建日志存档 CreateArchive() { string filename = Path.GetfilenameWithoutExtension(_currentStream.CurrentLogfilePath); CloseStream(); 关闭日志写入流 file.Move(_currentStream.CurrentLogfilePath,Path.Combine(_currentStream.CurrentLogfileDir,filename + _" + (++_currentStream.CurrentArchiveIndex) + .txt")); 存档 CreateStream(); 创建日志写入流 } #region UpdateCurrentStream 更新日志写入流 UpdateCurrentStream() { 关闭日志写入流 CloseStream(); 创建新的日志路径 _currentStream.CurrentDateStr = DateTime.Now.ToString(_dateFormat); _currentStream.CurrentLogfileDir = Path.Combine(_basePath,1)"> _logType.ToString()); _currentStream.CurrentLogfilePath = Path.Combine(_currentStream.CurrentLogfileDir,1)">); CreateStream(); 初始化 _currentArchiveIndex InitCurrentArchiveIndex(); 初始化 _currentfileSize InitCurrentfileSize(); } #region 写日志 写日志 </summary> <param name="log">日志内容</param> voID WriteLog( { log = CreateLogString(_logType,log); Writefile(log); } #endregion }}VIEw Code
静态类LogUtil类:
写日志类 LogUtil { #region 字段 static LogWriter _infoWriter = LogWriter(LogType.Info); static LogWriter _deBUGWriter = LogWriter(LogType.DeBUG); static LogWriter _errorWriter = LogWriter(LogType.Error); #region 写 *** 作日志 写 *** 作日志 voID Log( log) { _infoWriter.WriteLog(log); } #region 写调试日志 写调试日志 voID DeBUG( log) { _deBUGWriter.WriteLog(log); } #region 写错误日志 voID Error(Exception ex,1)">string log = ) { Error(string.IsNullOrEmpty(log) ? ex.Message + " + ex.StackTrace : (log + :") + ex.Message + ex.StackTrace); } 写错误日志 voID Error( log) { _errorWriter.WriteLog(log); } }}VIEw Code
测试代码(LogUtil、NLog、log4net写日志性能对比):
NLog; System.ComponentModel; System.Data; System.Diagnostics; System.Drawing; System.Threading.Tasks; System.windows.Forms; Utils; LogUtilTest{ partial Form1 : Form { private Logger _log = NLog.LogManager.GetCurrentClassLogger(); private log4net.ILog _log2 = ; int n = 300000 Form1() { InitializeComponent(); ThreadPool.SetMinThreads(20,1)">20); UriBuilder uri = UriBuilder(Assembly.GetExecutingAssembly().CodeBase); string path = Path.GetDirectoryname(Uri.UnescapeDataString(uri.Path)); fileInfo configfile = new fileInfo(Path.Combine(path,1)">log4net.config)); log4net.Config.XmlConfigurator.Configure(configfile); _log2 = log4net.LogManager.GetLogger(typeof(Form1)); } #region Log this.Isdisposed) { if (.Invokerequired) { this.BeginInvoke(new Action(() => { textBox1.AppendText(DateTime.Now.ToString(HH:mm:ss.fff") + " " + log + \r\n\r\n); })); } { textBox1.AppendText(DateTime.Now.ToString(); } } } voID button1_Click( sender,EventArgs e) { LogUtil.Log(测试写 Info 日志); LogUtil.DeBUG(测试写 DeBUG 日志); LogUtil.Error(测试写 Error 日志); } voID button2_Click( { Log(==== 开始 ========); Stopwatch stopwatch = Stopwatch(); stopwatch.Start(); List<Task> taskList = new List<Task>(); Task tsk = ; int taskCount = ; tsk = Task.Run(() => { for (int i = 0; i < n; i++) { LogUtil.Log(测试日志 " + i.ToString(000000)); Interlocked.Increment(ref taskCount); } }); taskList.Add(tsk); tsk = Task.Run(() =>) { LogUtil.DeBUG() { LogUtil.Error( taskCount); } }); taskList.Add(tsk); Task.WaitAll(taskList.ToArray()); Log(Task Count= taskCount); Log(==== 结束 " + ,耗时:" + stopwatch.Elapsed.TotalSeconds.ToString(0.000 秒 ========); stopwatch.Stop(); }); } 对比NLog voID button3_Click() { _log.Info() { _log.DeBUG() { _log.Error(对比log4net voID button4_Click() { _log2.Info() { _log2.DeBUG() { _log2.Error(); stopwatch.Stop(); }); } }}VIEw Code
log4net.config配置文件:
<?xml version="1.0" enCoding="utf-8"?><log4net> <!-- 日志文件配置--> root> level value="ALL"/> 按文件存储日志--> appender-ref ="DeBUGAppender"="InfoAppender"="ErrorAppender" /> </appender name="ErrorAppender" type="log4net.Appender.RollingfileAppender"param ="file" value=".\Logs\Error\" 日志记录的存在路="AppendTofile"="true" 为true就表示日志会附加到文件,为false,则会重新创建一个新文件="MaxSizeRollBackups"="100" 创建最大文件数="maximumfileSize"="10MB" 文件大小="StaticLogfilename"="false" 是否指定文件名="DatePattern"="yyyy-MM-dd".log""文件格式="RollingStyle"="Composite" 创建新文件的方式,可选为Size(按文件大小),Date(按日期),Once(每启动一次创建一个文件),Composite(按日期及文件大小),默认为Compositelayout type="log4net.Layout.PatternLayout"> 输出内容布局--> ="ConversionPattern"="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> method会影响性能layoutfilter ="log4net.Filter.LevelRangeFilter"="LevelMin"="ERROR" ="LevelMax"filterappender="InfoAppender"=".\Logs\Info\" ="yyyy-MM-dd".log"" ="INFO" ="DeBUGAppender"=".\Logs\DeBUG\" ="DEBUG" >>VIEw Code
NLog.config配置文件:
xml version="1.0" enCoding="utf-8" nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.nlog-project.org/schemas/NLog.xsd NLog.xsd" autoReload="true" throwExceptions="false" internalLogLevel="Off" internalLogfile="d:\nlog\nlog-internal.log"> optional,add some variables https://github.com/nlog/NLog/wiki/Configuration-file#variables <variable name="myvar" value="myvalue"/>--> variable ="logDir"="${basedir}/nlog"="logfilename"="${date:format=yyyyMMdd}.txt"="logArchivefilename"="${date:format=yyyyMMdd}_{#}.txt"="logLayout"="${date:format=yyyy-MM-dd HH\:mm\:ss.fff} [${level}] ${message}"/> See https://github.com/nlog/nlog/wiki/Configuration-file for information on customizing logging rules and outputs. targets> add your targets here See https://github.com/nlog/NLog/wiki/Targets for possible targets. See https://github.com/nlog/NLog/wiki/Layout-Renderers for the possible layout renderers. --> Write events to a file with the date in the filename. <target xsi:type="file" name="f" filename="${basedir}/logs/${shortdate}.log" layout="${longdate} ${uppercase:${level}} ${message}" /> target xsi:type name="info" layout="${logLayout}" filename="${logDir}/info/${logfilename}" archivefilename="${logDir}/info/${logArchivefilename}" archiveAboveSize="10485760" archiveNumbering="Sequence" maxArchivefiles="100" concurrentWrites keepfileOpen openfileCacheTimeout="30" enCoding="UTF-8" /> ="deBUG"="${logDir}/deBUG/${logfilename}"="${logDir}/deBUG/${logArchivefilename}"="error"="${logDir}/error/${logfilename}"="${logDir}/error/${logArchivefilename}"rules add your logging rules here Write all events with minimal level of DeBUG (So DeBUG,Warn,Error and Fatal,but not Trace) to "f" <logger name="*" minlevel="DeBUG" writeto="f" /> logger ="*" minlevel="Info" maxlevel writeto="info" ="DeBUG"="deBUG" ="Error"="error" nlog>VIEw Code
测试截图:
写Info、DeBUG、Error日志各30万行,LogUtil耗时4.628秒,NLog耗时4.900秒,log4net耗时10.564秒,硬盘是固态硬盘。
说明:
该版本不支持多进程并发。
支持多进程并发的LogWriter版本(注意:代码中要加上 _currentStream.CurrentfileStream.Seek(0,SeekOrigin.End); 这句,不然不支持多进程并发):
支持多进程并发写日志的LogWriter版本 LogWriterUseMutex { Mutex _mutex; LogWriterUseMutex(LogType logType) { _logType = logType; _mutex = new Mutex(false,1)">Mutex.LogWriter..7693FFAD38004F6B8FD31F6A8B4CE2BD); Init(); } { _mutex.WaitOne(); 判断是否更新Stream DateTime.Now.ToString(_dateFormat); dateStr) { _currentStream.CurrentDateStr = dateStr; UpdateCurrentStream(); } 判断是否创建Archive EnCoding.UTF8.GetByteCount(log); _currentStream.CurrentfileSize += byteCount; _fileSize) { _currentStream.CurrentfileSize = ; CreateArchive(); } 日志内容写入文件 _currentStream.CurrentfileStream.Seek(,SeekOrigin.End); _currentStream.CurrentStreamWriter.Writeline(log); _currentStream.CurrentStreamWriter.Flush(); } ex.StackTrace); } finally { _mutex.ReleaseMutex(); } } }}VIEw Code
多进程并发的版本,性能差一些。
有BUG,file.Move这行代码多进程并发会异常,因文件一直是打开状态的,所以这种实现方式可能无法解决这个BUG。
总结:
新版本比旧版本代码逻辑更简单,代码组织更合理。
一个方法的代码行数不宜太长,逻辑要简单,这样不容易出BUG;单线程相比多线程,不容易出BUG。
自己写的代价很大,花了整整一天时间,用来练手没问题,但是不经过一两个项目的实际使用以验证没有BUG的话,你敢用吗?
总结
以上是内存溢出为你收集整理的C#写日志工具类(新版)全部内容,希望文章能够帮你解决C#写日志工具类(新版)所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)