微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

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;
 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&quot;.log&quot;"文件格式="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&quot;.log&quot;" ="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 @R_178_4045@ion 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的话,你敢用吗?

 

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 [email protected] 举报,一经查实,本站将立刻删除。

相关推荐