C#写日志工具类(新版)

  • A+
所属分类:.NET技术
摘要

昨天打算把我以前写的一个C#写日志工具类放到GitHub上,却发现了一个BUG,当然,已经修复了。

昨天打算把我以前写的一个C#写日志工具类放到GitHub上,却发现了一个BUG,当然,已经修复了。

然后写Demo对比了NLog和log4net,发现我这个LogUtil比它们性能低了不止一个数量级。工作多年,平时都是用别人写的库,自己写的很少。因为当初自己没有时间研究log4net或NLog,并且写个简单的日志工具类自己也有能力实现,所以就自己写了LogUtil自己用,修修改改了很多次了,居然还是有BUG,因为用了多线程和锁,BUG还很隐蔽,而且性能还比较差,代码写的很挫。因为逻辑复杂,更容易出BUG。用NLog或log4net它不香吗?但又心有不甘,而且对于自己写的一些小的程序,可能第三方日志类库的dll比自己的程序都大,所以也有必要自己写一个,以便平时写各种Demo用。

之前写的很挫,逻辑很复杂的日志工具类:https://www.cnblogs.com/s0611163/p/4023859.html

日志类型LogType类:

C#写日志工具类(新版)C#写日志工具类(新版)

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     } }

View Code

当前日志写入流LogStream类:

C#写日志工具类(新版)C#写日志工具类(新版)

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; }     } }

View Code

LogWriter类:

C#写日志工具类(新版)C#写日志工具类(新版)

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"; //日志文件夹名称          #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             {                 //判断是否更新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 + "rn" + 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 + "rn" + 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 + "rn" + ex.StackTrace);             }         }         #endregion      } }

View Code

静态类LogUtil类:

C#写日志工具类(新版)C#写日志工具类(新版)

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 + "rn" + ex.StackTrace : (log + "") + ex.Message + "rn" + ex.StackTrace);         }          /// <summary>         /// 写错误日志         /// </summary>         public static void Error(string log)         {             _errorWriter.WriteLog(log);         }         #endregion      }  }

View Code

测试代码(LogUtil、NLog、log4net写日志性能对比):

C#写日志工具类(新版)C#写日志工具类(新版)

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 + "rnrn");                     }));                 }                 else                 {                     textBox1.AppendText(DateTime.Now.ToString("HH:mm:ss.fff") + " " + log + "rnrn");                 }             }         }         #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();             });         }      } }

View Code

log4net.config配置文件:

C#写日志工具类(新版)C#写日志工具类(新版)

<?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&quot;.log&quot;"/>     <!--文件格式-->     <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&quot;.log&quot;" />     <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&quot;.log&quot;" />     <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>

View Code

NLog.config配置文件:

C#写日志工具类(新版)C#写日志工具类(新版)

<?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:nlognlog-internal.log">    <!-- optional, add some variables   https://github.com/nlog/NLog/wiki/Configuration-file#variables   -->   <variable name="myvar" value="myvalue"/>   <variable name="rootFolder" value="nlog"/>    <!--   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" fileName="${basedir}/${rootFolder}/info/${shortdate}.log"             layout="${longdate} ${uppercase:${level}} ${message}"             archiveFileName="${basedir}/${rootFolder}/info/${shortdate}-{#####}.log"             archiveAboveSize="10485760"             archiveNumbering="Sequence"             maxArchiveFiles="100"             concurrentWrites="true"             keepFileOpen="true"             openFileCacheTimeout="30"             encoding="UTF-8" />      <target xsi:type="File" name="debug" fileName="${basedir}/${rootFolder}/debug/${shortdate}.log"             layout="${longdate} ${uppercase:${level}} ${message}"             archiveFileName="${basedir}/${rootFolder}/debug/${shortdate}-{#####}.log"             archiveAboveSize="10485760"             archiveNumbering="Sequence"             maxArchiveFiles="100"             concurrentWrites="true"             keepFileOpen="true"             openFileCacheTimeout="30"             encoding="UTF-8" />      <target xsi:type="File" name="error" fileName="${basedir}/${rootFolder}/error/${shortdate}.log"             layout="${longdate} ${uppercase:${level}} ${message}"             archiveFileName="${basedir}/${rootFolder}/error/${shortdate}-{#####}.log"             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>

View Code

测试截图:

C#写日志工具类(新版)

写Info、Debug、Error日志各30万行,LogUtil耗时4.562秒,NLog耗时4.979秒,log4net耗时11.797秒,硬盘是固态硬盘。

 

总结:

一个方法的代码行数不宜太长,逻辑要简单,不容易出BUG;单线程相比多线程,不容易出BUG。