昨天打算把我以前寫的一個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; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Utils { /// <summary> /// 日志類型 /// </summary> public enum LogType { Debug, Info, Error } }
當前日志寫入流LogStream類:

using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; namespace Utils { internal class LogStream { public FileStream CurrentFileStream { get; set; } public StreamWriter CurrentStreamWriter { get; set; } public int CurrentArchiveIndex { get; set; } public long CurrentFileSize { get; set; } public string CurrentDateStr { get; set; } public string CurrentLogFilePath { get; set; } public string CurrentLogFileDir { get; set; } } }
LogWriter類:

using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace Utils { internal class LogWriter { #region 字段屬性 private LogType _logType; private string _basePath; private int _fileSize = 10 * 1024 * 1024; //日志分隔文件大小 private LogStream _currentStream = new LogStream(); private string _dateFormat = "yyyyMMdd"; //日志文件名日期格式化 private string _rootFolder = "Log"; //日志文件夾名稱 private object _lockWriter = new object(); #endregion #region LogWriter public LogWriter(LogType logType) { _logType = logType; Init(); } #endregion #region Init /// <summary> /// 初始化 /// </summary> private void Init() { //初始化 _basePath InitBasePath(); //創建目錄 CreateLogDir(); //更新日志寫入流 UpdateCurrentStream(); } #endregion #region 初始化 _basePath /// <summary> /// 初始化 _basePath /// </summary> private void InitBasePath() { UriBuilder uri = new UriBuilder(Assembly.GetExecutingAssembly().CodeBase); _basePath = Path.GetDirectoryName(Uri.UnescapeDataString(uri.Path)); } #endregion #region 初始化 _currentArchiveIndex /// <summary> /// 初始化 _currentArchiveIndex /// </summary> private void 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 (!string.IsNullOrWhiteSpace(str)) { int temp = Convert.ToInt32(str); if (temp > _currentStream.CurrentArchiveIndex) { _currentStream.CurrentArchiveIndex = temp; } } else { _currentStream.CurrentArchiveIndex = 0; } } } } #endregion #region 初始化 _currentFileSize /// <summary> /// 初始化 _currentFileSize /// </summary> private void InitCurrentFileSize() { FileInfo fileInfo = new FileInfo(_currentStream.CurrentLogFilePath); _currentStream.CurrentFileSize = fileInfo.Length; } #endregion #region CreateLogDir() /// <summary> /// 創建日志目錄 /// </summary> private void CreateLogDir() { string logDir = Path.Combine(_basePath, _rootFolder + "\\" + _logType.ToString()); if (!Directory.Exists(logDir)) { Directory.CreateDirectory(logDir); } } #endregion #region CreateStream /// <summary> /// 創建日志寫入流 /// </summary> private void CreateStream() { _currentStream.CurrentFileStream = new FileStream(_currentStream.CurrentLogFilePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); _currentStream.CurrentStreamWriter = new StreamWriter(_currentStream.CurrentFileStream, Encoding.UTF8); } #endregion #region CloseStream /// <summary> /// 關閉日志寫入流 /// </summary> private void CloseStream() { if (_currentStream.CurrentStreamWriter != null) { _currentStream.CurrentStreamWriter.Close(); } if (_currentStream.CurrentFileStream != null) { _currentStream.CurrentFileStream.Close(); } } #endregion #region 拼接日志內容 /// <summary> /// 拼接日志內容 /// </summary> private static string CreateLogString(LogType logType, string log) { return string.Format(@"{0} {1} {2}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), ("[" + logType.ToString() + "]").PadRight(7, ' '), log); } #endregion #region 寫文件 /// <summary> /// 寫文件 /// </summary> private void WriteFile(string log) { 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 = 0; CreateArchive(); } //日志內容寫入文件 _currentStream.CurrentStreamWriter.WriteLine(log); _currentStream.CurrentStreamWriter.Flush(); } } catch (Exception ex) { Console.WriteLine(ex.Message + "\r\n" + ex.StackTrace); } } #endregion #region CreateArchive /// <summary> /// 創建日志存檔 /// </summary> private void CreateArchive() { string fileName = Path.GetFileNameWithoutExtension(_currentStream.CurrentLogFilePath); CloseStream(); //關閉日志寫入流 File.Move(_currentStream.CurrentLogFilePath, Path.Combine(_currentStream.CurrentLogFileDir, fileName + "_" + (++_currentStream.CurrentArchiveIndex) + ".txt")); //存檔 CreateStream(); //創建日志寫入流 } #endregion #region UpdateCurrentStream /// <summary> /// 更新日志寫入流 /// </summary> private void UpdateCurrentStream() { try { //關閉日志寫入流 CloseStream(); //創建新的日志路徑 _currentStream.CurrentDateStr = DateTime.Now.ToString(_dateFormat); _currentStream.CurrentLogFileDir = Path.Combine(_basePath, _rootFolder + "\\" + _logType.ToString()); _currentStream.CurrentLogFilePath = Path.Combine(_currentStream.CurrentLogFileDir, _currentStream.CurrentDateStr + ".txt"); //創建日志寫入流 CreateStream(); //初始化 _currentArchiveIndex InitCurrentArchiveIndex(); //初始化 _currentFileSize InitCurrentFileSize(); } catch (Exception ex) { Console.WriteLine(ex.Message + "\r\n" + ex.StackTrace); } } #endregion #region 寫日志 /// <summary> /// 寫日志 /// </summary> /// <param name="log">日志內容</param> public void WriteLog(string log) { try { log = CreateLogString(_logType, log); WriteFile(log); } catch (Exception ex) { Console.WriteLine(ex.Message + "\r\n" + ex.StackTrace); } } #endregion } }
靜態類LogUtil類:

using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace Utils { /// <summary> /// 寫日志類 /// </summary> public class LogUtil { #region 字段 private static LogWriter _infoWriter = new LogWriter(LogType.Info); private static LogWriter _debugWriter = new LogWriter(LogType.Debug); private static LogWriter _errorWriter = new LogWriter(LogType.Error); #endregion #region 寫操作日志 /// <summary> /// 寫操作日志 /// </summary> public static void Log(string log) { _infoWriter.WriteLog(log); } #endregion #region 寫調試日志 /// <summary> /// 寫調試日志 /// </summary> public static void Debug(string log) { _debugWriter.WriteLog(log); } #endregion #region 寫錯誤日志 public static void Error(Exception ex, string log = null) { Error(string.IsNullOrEmpty(log) ? ex.Message + "\r\n" + ex.StackTrace : (log + ":") + ex.Message + "\r\n" + ex.StackTrace); } /// <summary> /// 寫錯誤日志 /// </summary> public static void Error(string log) { _errorWriter.WriteLog(log); } #endregion } }
測試代碼(LogUtil、NLog、log4net寫日志性能對比):

using NLog; using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Diagnostics; using System.Drawing; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using Utils; namespace LogUtilTest { public partial class Form1 : Form { private Logger _log = NLog.LogManager.GetCurrentClassLogger(); private log4net.ILog _log2 = null; private int n = 300000; public Form1() { InitializeComponent(); ThreadPool.SetMinThreads(20, 20); UriBuilder uri = new UriBuilder(Assembly.GetExecutingAssembly().CodeBase); string path = Path.GetDirectoryName(Uri.UnescapeDataString(uri.Path)); FileInfo configFile = new FileInfo(Path.Combine(path, "log4net.config")); log4net.Config.XmlConfigurator.Configure(configFile); _log2 = log4net.LogManager.GetLogger(typeof(Form1)); } #region Log private void Log(string log) { if (!this.IsDisposed) { if (this.InvokeRequired) { this.BeginInvoke(new Action(() => { textBox1.AppendText(DateTime.Now.ToString("HH:mm:ss.fff") + " " + log + "\r\n\r\n"); })); } else { textBox1.AppendText(DateTime.Now.ToString("HH:mm:ss.fff") + " " + log + "\r\n\r\n"); } } } #endregion private void button1_Click(object sender, EventArgs e) { LogUtil.Log("測試寫 Info 日志"); LogUtil.Debug("測試寫 Debug 日志"); LogUtil.Error("測試寫 Error 日志"); } private void button2_Click(object sender, EventArgs e) { Task.Run(() => { Log("==== 開始 ========"); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); List<Task> taskList = new List<Task>(); Task tsk = null; int taskCount = 0; 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(() => { for (int i = 0; i < n; i++) { LogUtil.Debug("測試日志 " + i.ToString("000000")); Interlocked.Increment(ref taskCount); } }); taskList.Add(tsk); tsk = Task.Run(() => { for (int i = 0; i < n; i++) { LogUtil.Error("測試日志 " + i.ToString("000000")); Interlocked.Increment(ref taskCount); } }); taskList.Add(tsk); Task.WaitAll(taskList.ToArray()); Log("Task Count=" + taskCount); Log("==== 結束 " + ",耗時:" + stopwatch.Elapsed.TotalSeconds.ToString("0.000") + " 秒 ========"); stopwatch.Stop(); }); } //對比NLog private void button3_Click(object sender, EventArgs e) { Task.Run(() => { Log("==== 開始 ========"); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); List<Task> taskList = new List<Task>(); Task tsk = null; int taskCount = 0; tsk = Task.Run(() => { for (int i = 0; i < n; i++) { _log.Info("測試日志 " + i.ToString("000000")); Interlocked.Increment(ref taskCount); } }); taskList.Add(tsk); tsk = Task.Run(() => { for (int i = 0; i < n; i++) { _log.Debug("測試日志 " + i.ToString("000000")); Interlocked.Increment(ref taskCount); } }); taskList.Add(tsk); tsk = Task.Run(() => { for (int i = 0; i < n; i++) { _log.Error("測試日志 " + i.ToString("000000")); Interlocked.Increment(ref taskCount); } }); taskList.Add(tsk); Task.WaitAll(taskList.ToArray()); Log("Task Count=" + taskCount); Log("==== 結束 " + ",耗時:" + stopwatch.Elapsed.TotalSeconds.ToString("0.000") + " 秒 ========"); stopwatch.Stop(); }); } //對比log4net private void button4_Click(object sender, EventArgs e) { Task.Run(() => { Log("==== 開始 ========"); Stopwatch stopwatch = new Stopwatch(); stopwatch.Start(); List<Task> taskList = new List<Task>(); Task tsk = null; int taskCount = 0; tsk = Task.Run(() => { for (int i = 0; i < n; i++) { _log2.Info("測試日志 " + i.ToString("000000")); Interlocked.Increment(ref taskCount); } }); taskList.Add(tsk); tsk = Task.Run(() => { for (int i = 0; i < n; i++) { _log2.Debug("測試日志 " + i.ToString("000000")); Interlocked.Increment(ref taskCount); } }); taskList.Add(tsk); tsk = Task.Run(() => { for (int i = 0; i < n; i++) { _log2.Error("測試日志 " + i.ToString("000000")); Interlocked.Increment(ref taskCount); } }); taskList.Add(tsk); Task.WaitAll(taskList.ToArray()); Log("Task Count=" + taskCount); Log("==== 結束 " + ",耗時:" + stopwatch.Elapsed.TotalSeconds.ToString("0.000") + " 秒 ========"); stopwatch.Stop(); }); } } }
log4net.config配置文件:

<?xml version="1.0" encoding="utf-8"?> <log4net> <!-- 日志文件配置--> <root> <level value="ALL"/> <!--按文件存儲日志--> <appender-ref ref="DebugAppender"/> <appender-ref ref="InfoAppender"/> <appender-ref ref="ErrorAppender" /> </root> <appender name="ErrorAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value=".\\Logs\\Error\\" /> <!--日志記錄的存在路--> <param name="AppendToFile" value="true" /> <!--為true就表示日志會附加到文件,為false,則會重新創建一個新文件--> <param name="MaxSizeRollBackups" value="100" /> <!--創建最大文件數--> <param name="maximumFileSize" value="10MB" /> <!--文件大小--> <param name="StaticLogFileName" value="false" /> <!--是否指定文件名--> <param name="DatePattern" value="yyyy-MM-dd".log""/> <!--文件格式--> <param name="RollingStyle" value="Composite" /> <!--創建新文件的方式,可選為Size(按文件大小),Date(按日期),Once(每啟動一次創建一個文件),Composite(按日期及文件大小),默認為Composite--> <layout type="log4net.Layout.PatternLayout"> <!--輸出內容布局--> <param name="ConversionPattern" value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> <!--method會影響性能--> </layout> <filter type="log4net.Filter.LevelRangeFilter"> <param name="LevelMin" value="ERROR" /> <param name="LevelMax" value="ERROR" /> </filter> </appender> <appender name="InfoAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value=".\\Logs\\Info\\" /> <param name="AppendToFile" value="true" /> <param name="MaxSizeRollBackups" value="100" /> <param name="maximumFileSize" value="10MB" /> <param name="StaticLogFileName" value="false" /> <param name="DatePattern" value="yyyy-MM-dd".log"" /> <param name="RollingStyle" value="Composite" /> <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> </layout> <filter type="log4net.Filter.LevelRangeFilter"> <param name="LevelMin" value="INFO" /> <param name="LevelMax" value="INFO" /> </filter> </appender> <appender name="DebugAppender" type="log4net.Appender.RollingFileAppender"> <param name="File" value=".\\Logs\\Debug\\" /> <param name="AppendToFile" value="true" /> <param name="MaxSizeRollBackups" value="100" /> <param name="maximumFileSize" value="10MB" /> <param name="StaticLogFileName" value="false" /> <param name="DatePattern" value="yyyy-MM-dd".log"" /> <param name="RollingStyle" value="Composite" /> <layout type="log4net.Layout.PatternLayout"> <param name="ConversionPattern" value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline" /> </layout> <filter type="log4net.Filter.LevelRangeFilter"> <param name="LevelMin" value="DEBUG" /> <param name="LevelMax" value="DEBUG" /> </filter> </appender> </log4net>
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 name="logDir" value="${basedir}/nlog"/> <variable name="logFileName" value="${date:format=yyyyMMdd}.txt"/> <variable name="logArchiveFileName" value="${date:format=yyyyMMdd}_{#}.txt"/> <variable name="logLayout" value="${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="File" name="info" layout="${logLayout}" fileName="${logDir}/info/${logFileName}" archiveFileName="${logDir}/info/${logArchiveFileName}" archiveAboveSize="10485760" archiveNumbering="Sequence" maxArchiveFiles="100" concurrentWrites="true" keepFileOpen="true" openFileCacheTimeout="30" encoding="UTF-8" /> <target xsi:type="File" name="debug" layout="${logLayout}" fileName="${logDir}/debug/${logFileName}" archiveFileName="${logDir}/debug/${logArchiveFileName}" archiveAboveSize="10485760" archiveNumbering="Sequence" maxArchiveFiles="100" concurrentWrites="true" keepFileOpen="true" openFileCacheTimeout="30" encoding="UTF-8" /> <target xsi:type="File" name="error" layout="${logLayout}" fileName="${logDir}/error/${logFileName}" archiveFileName="${logDir}/error/${logArchiveFileName}" archiveAboveSize="10485760" archiveNumbering="Sequence" maxArchiveFiles="100" concurrentWrites="true" keepFileOpen="true" openFileCacheTimeout="30" encoding="UTF-8" /> </targets> <rules> <!-- add your logging rules here --> <!-- Write all events with minimal level of Debug (So Debug, Info, Warn, Error and Fatal, but not Trace) to "f" <logger name="*" minlevel="Debug" writeTo="f" /> --> <logger name="*" minlevel="Info" maxlevel="Info" writeTo="info" /> <logger name="*" minlevel="Debug" maxlevel="Debug" writeTo="debug" /> <logger name="*" minlevel="Error" maxlevel="Error" writeTo="error" /> </rules> </nlog>
測試截圖:
寫Info、Debug、Error日志各30萬行,LogUtil耗時4.628秒,NLog耗時4.900秒,log4net耗時10.564秒,硬盤是固態硬盤。
說明:
該版本不支持多進程並發。
支持多進程並發的LogWriter版本(注意:代碼中要加上 _currentStream.CurrentFileStream.Seek(0, SeekOrigin.End); 這句,不然不支持多進程並發):

using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; using System.Reflection; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; namespace Utils { /// <summary> /// 支持多進程並發寫日志的LogWriter版本 /// </summary> internal class LogWriterUseMutex { #region 字段屬性 private LogType _logType; private string _basePath; private int _fileSize = 10 * 1024 * 1024; //日志分隔文件大小 private LogStream _currentStream = new LogStream(); private string _dateFormat = "yyyyMMdd"; //日志文件名日期格式化 private string _rootFolder = "Log"; //日志文件夾名稱 private Mutex _mutex; #endregion #region LogWriter public LogWriterUseMutex(LogType logType) { _logType = logType; _mutex = new Mutex(false, "Mutex.LogWriter." + logType.ToString() + ".7693FFAD38004F6B8FD31F6A8B4CE2BD"); Init(); } #endregion #region Init /// <summary> /// 初始化 /// </summary> private void Init() { //初始化 _basePath InitBasePath(); //創建目錄 CreateLogDir(); //更新日志寫入流 UpdateCurrentStream(); } #endregion #region 初始化 _basePath /// <summary> /// 初始化 _basePath /// </summary> private void InitBasePath() { UriBuilder uri = new UriBuilder(Assembly.GetExecutingAssembly().CodeBase); _basePath = Path.GetDirectoryName(Uri.UnescapeDataString(uri.Path)); } #endregion #region 初始化 _currentArchiveIndex /// <summary> /// 初始化 _currentArchiveIndex /// </summary> private void 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 (!string.IsNullOrWhiteSpace(str)) { int temp = Convert.ToInt32(str); if (temp > _currentStream.CurrentArchiveIndex) { _currentStream.CurrentArchiveIndex = temp; } } else { _currentStream.CurrentArchiveIndex = 0; } } } } #endregion #region 初始化 _currentFileSize /// <summary> /// 初始化 _currentFileSize /// </summary> private void InitCurrentFileSize() { FileInfo fileInfo = new FileInfo(_currentStream.CurrentLogFilePath); _currentStream.CurrentFileSize = fileInfo.Length; } #endregion #region CreateLogDir() /// <summary> /// 創建日志目錄 /// </summary> private void CreateLogDir() { string logDir = Path.Combine(_basePath, _rootFolder + "\\" + _logType.ToString()); if (!Directory.Exists(logDir)) { Directory.CreateDirectory(logDir); } } #endregion #region CreateStream /// <summary> /// 創建日志寫入流 /// </summary> private void CreateStream() { _currentStream.CurrentFileStream = new FileStream(_currentStream.CurrentLogFilePath, FileMode.Append, FileAccess.Write, FileShare.ReadWrite); _currentStream.CurrentStreamWriter = new StreamWriter(_currentStream.CurrentFileStream, Encoding.UTF8); } #endregion #region CloseStream /// <summary> /// 關閉日志寫入流 /// </summary> private void CloseStream() { if (_currentStream.CurrentStreamWriter != null) { _currentStream.CurrentStreamWriter.Close(); } if (_currentStream.CurrentFileStream != null) { _currentStream.CurrentFileStream.Close(); } } #endregion #region 拼接日志內容 /// <summary> /// 拼接日志內容 /// </summary> private static string CreateLogString(LogType logType, string log) { return string.Format(@"{0} {1} {2}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss.fff"), ("[" + logType.ToString() + "]").PadRight(7, ' '), log); } #endregion #region 寫文件 /// <summary> /// 寫文件 /// </summary> private void WriteFile(string log) { try { _mutex.WaitOne(); //判斷是否更新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 = 0; CreateArchive(); } //日志內容寫入文件 _currentStream.CurrentFileStream.Seek(0, SeekOrigin.End); _currentStream.CurrentStreamWriter.WriteLine(log); _currentStream.CurrentStreamWriter.Flush(); } catch (Exception ex) { Console.WriteLine(ex.Message + "\r\n" + ex.StackTrace); } finally { _mutex.ReleaseMutex(); } } #endregion #region CreateArchive /// <summary> /// 創建日志存檔 /// </summary> private void CreateArchive() { string fileName = Path.GetFileNameWithoutExtension(_currentStream.CurrentLogFilePath); CloseStream(); //關閉日志寫入流 File.Move(_currentStream.CurrentLogFilePath, Path.Combine(_currentStream.CurrentLogFileDir, fileName + "_" + (++_currentStream.CurrentArchiveIndex) + ".txt")); //存檔 CreateStream(); //創建日志寫入流 } #endregion #region UpdateCurrentStream /// <summary> /// 更新日志寫入流 /// </summary> private void UpdateCurrentStream() { try { //關閉日志寫入流 CloseStream(); //創建新的日志路徑 _currentStream.CurrentDateStr = DateTime.Now.ToString(_dateFormat); _currentStream.CurrentLogFileDir = Path.Combine(_basePath, _rootFolder + "\\" + _logType.ToString()); _currentStream.CurrentLogFilePath = Path.Combine(_currentStream.CurrentLogFileDir, _currentStream.CurrentDateStr + ".txt"); //創建日志寫入流 CreateStream(); //初始化 _currentArchiveIndex InitCurrentArchiveIndex(); //初始化 _currentFileSize InitCurrentFileSize(); } catch (Exception ex) { Console.WriteLine(ex.Message + "\r\n" + ex.StackTrace); } } #endregion #region 寫日志 /// <summary> /// 寫日志 /// </summary> /// <param name="log">日志內容</param> public void WriteLog(string log) { try { log = CreateLogString(_logType, log); WriteFile(log); } catch (Exception ex) { Console.WriteLine(ex.Message + "\r\n" + ex.StackTrace); } } #endregion } }
多進程並發的版本,性能差一些。
有BUG,File.Move這行代碼多進程並發會異常,因文件一直是打開狀態的,所以這種實現方式可能無法解決這個BUG。
總結:
新版本比舊版本代碼邏輯更簡單,代碼組織更合理。
一個方法的代碼行數不宜太長,邏輯要簡單,這樣不容易出BUG;單線程相比多線程,不容易出BUG。
自己寫的代價很大,花了整整一天時間,用來練手沒問題,但是不經過一兩個項目的實際使用以驗證沒有BUG的話,你敢用嗎?