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


    昨天打算把我以前写的一个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
        }
    }
    View Code

    当前日志写入流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; }
        }
    }
    View Code

    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 + "
    " + 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 + "
    " + 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 + "
    " + ex.StackTrace);
                }
            }
            #endregion
    
        }
    }
    View Code

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

    测试代码(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 + "
    
    ");
                        }));
                    }
                    else
                    {
                        textBox1.AppendText(DateTime.Now.ToString("HH:mm:ss.fff") + " " + log + "
    
    ");
                    }
                }
            }
            #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配置文件:

    <?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配置文件:

    <?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:
    log
    log-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>
    View Code

    测试截图:

    写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 + "
    " + 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 + "
    " + 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 + "
    " + ex.StackTrace);
                }
            }
            #endregion
    
        }
    }
    View Code

    多进程并发的版本,性能差一些。

    有BUG,File.Move这行代码多进程并发会异常,因文件一直是打开状态的,所以这种实现方式可能无法解决这个BUG。

    总结:

    新版本比旧版本代码逻辑更简单,代码组织更合理。

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

    自己写的代价很大,花了整整一天时间,用来练手没问题,但是不经过一两个项目的实际使用以验证没有BUG的话,你敢用吗?

     

  • 相关阅读:
    win10如何在局域网中设置一台电脑的固定ip地址
    智能电视软件安装(WIFI上网)
    路由器连接宽带(成功上网步骤方法)
    FastReport.Net使用:[5]主从表
    FastReport.Net使用:[4]分组
    FastReport.Net使用:[3]简单报表一
    FastReport.Net使用:[2]添加MSSQL数据源一
    FastReport.Net使用:[1]屏蔽打印对话框
    如何配置FastReport.Net环境
    如何安装使用FastReport
  • 原文地址:https://www.cnblogs.com/s0611163/p/15371973.html
Copyright © 2020-2023  润新知