通過編寫一個簡單的日誌類庫來加深瞭解C#的檔案訪問控制
在程式的開發除錯過程及釋出執行後的狀態監控中,日誌都有著極其重要的分量,通過在關鍵邏輯節點將關鍵資料記錄到日誌檔案當中能幫助我們儘快找到程式問題所在。網上有不少專業成熟的日誌元件可用,比如log4net和nlog等,由其專業及受歡迎程度可見日誌在一個程式中的重要性。
我只用過log4net,而在用log4net寫日誌的過程中慢慢覺著太繁瑣了點,不就寫個日誌嗎?為毛搞得那麼複雜?各種配置讓我有點抓狂。
於是我就想,自己來吧!
首先分析一下一個基本的日誌類庫應該具有的基本功能及實現的時候需要注意和解決的問題:
1.關於日誌檔案的寫入
寫日誌並不是簡單的開啟一個檔案然後寫入資料然後關閉了事,無論是web程式還是桌面程式,首要問題是多執行緒爭搶寫入一個日誌檔案的訪問控制,次要問題是要允許其它程序在寫入程序未釋放日誌檔案時日誌檔案能被讀取——試想如果日誌在寫的時候不能被讀取那日誌將毫無價值。
為了解決多執行緒寫入的問題,多執行緒寫入的資料將被快取在一個StringBuilder物件中,而後由一個專門的寫檔案執行緒來負責取出資料寫入到日誌檔案,以此來保證只有一個執行緒對日誌檔案進行寫操作,如果再解決在檔案流未關閉的情況下讓其它程序或執行緒能讀取日誌內容,那問題就都不是問題了,而在檔案流未關閉的情況下要讓其它程序或執行緒能讀取日誌內容只需要在開啟或建立日誌檔案的FileStream時指定System.IO.FileShare引數為Read即可。
2.關於日誌檔案的讀取
檔案寫入成功後會有讀取進行檢視及分析的需求。內容較少的時候直接記事本開啟即可,但是日誌較大的時候就費勁了,雖然也有一些專門的軟體能開啟大文字檔案,可開啟日誌檔案有時並不是只為了看上一眼而已,很可能需要提取一些受關注的資料做個統計分析,比如提取某個操作的耗時來做瓶頸參考,因此有必要實現對大文字檔案的讀取,在讀取過程中進行資料的留存分析。
對大文字檔案的讀取當然要按塊來讀取,比如一次讀取10M位元組,這樣即便是幾個G的檔案也沒幾次可讀的,重要的是不能截斷單詞和寬字元,所以每讀取到指定位元組數(如10M位元組)的資料後需要根據指定的參考字元(如換行符、空格、逗號、句號等)做偏移計算。
對檔案的讀取在建立檔案的讀取流的時候必須要指定System.IO.FileShare引數為ReadWrite,否則對正在被寫入或未被釋放的檔案的訪問將被拒絕,因為寫入的程序已經獲得了寫入許可權,作為後來的讀取者一定要允許其它程序可以對檔案讀寫,要不然衝突就是一定的了。
3.關於日誌的清理
隨著程式常年執行,日誌積累得越來越多,而日誌應該都有一定的時效性,過了時效期後的日誌就沒有什麼價值了,所以應該對日誌做定時的清理操作,因此寫日誌的時候應該有一個預設的時效值,使日誌在到期之後自動刪除,以免無限增多浪費了磁碟空間,畢竟磁碟空間是十分有限的。
下面開始上程式碼:
新建一個 .Net Standard 類庫,命名 Logger ,在類庫中新增一個 Core 資料夾,在 Core 資料夾新增以下檔案:
- ILog.cs 介面
- Log.cs 密封的介面實現類(不對程式集外提供訪問)
- TextFileReader.cs 文字檔案讀取
- Factory.cs 工廠類(生產和維護日誌物件)
1 namespace Logger.Core 2 { 3 public interface ILog 4 { 5 void Write(string logInfo); 6 void WriteFormat(string format, params object[] args); 7 void SaveLogToFile(); 8 void ClearLogFile(); 9 } 10 }ILog.cs
1 namespace Logger.Core 2 { 3 internal class Log : ILog 4 { 5 private System.Text.StringBuilder logSource = null; 6 private string logFilePre = string.Empty; 7 private System.IO.FileStream fileStream = null; 8 private DateTime logFileScanLastTime = DateTime.Now; 9 private int logFileRetentionDays = 90; 10 11 12 public Log(string logFilePre) 13 : this(logFilePre, 90) 14 { 15 16 } 17 public Log(string logFilePre, int logFileRetentionDays) 18 { 19 this.logFilePre = logFilePre; 20 this.logSource = new System.Text.StringBuilder(); 21 if (logFileRetentionDays < 1) 22 { 23 logFileRetentionDays = 1; 24 } 25 this.logFileRetentionDays = logFileRetentionDays; 26 Factory.SetFileThreadStart(); 27 } 28 29 30 private System.IO.FileStream GetFileStream() 31 { 32 if (!System.IO.Directory.Exists(Factory.logsDirPath)) 33 { 34 System.IO.Directory.CreateDirectory(Factory.logsDirPath); 35 } 36 System.IO.FileStream fs; 37 string FilePath = System.IO.Path.Combine(Factory.logsDirPath, this.logFilePre + DateTime.Now.ToString("yyyyMMdd") + ".log"); 38 if (!System.IO.File.Exists(FilePath)) 39 { 40 if (fileStream != null) 41 { 42 fileStream.Close(); 43 } 44 fileStream = fs = new System.IO.FileStream(FilePath, System.IO.FileMode.CreateNew, System.IO.FileAccess.Write, System.IO.FileShare.Read, 1024, true); 45 } 46 else 47 { 48 if (fileStream != null) 49 { 50 fs = fileStream; 51 } 52 else 53 { 54 fileStream = fs = new System.IO.FileStream(FilePath, System.IO.FileMode.Open, System.IO.FileAccess.Write, System.IO.FileShare.Read, 1024, true); 55 } 56 } 57 return fs; 58 } 59 private string GetLogText() 60 { 61 string s = ""; 62 if (logSource.Length > 0) 63 { 64 lock (logSource) 65 { 66 s = logSource.ToString(); 67 logSource.Clear(); 68 } 69 } 70 return s; 71 } 72 73 74 public void Write(string logInfo) 75 { 76 try 77 { 78 if (logSource == null) 79 { 80 logSource = new System.Text.StringBuilder(); 81 } 82 lock (this) 83 { 84 logSource.AppendFormat("{0} {1}{2}", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff"), logInfo, System.Environment.NewLine); 85 } 86 } 87 catch { } 88 } 89 public void WriteFormat(string format, params object[] args) 90 { 91 try 92 { 93 if (logSource == null) 94 { 95 logSource = new System.Text.StringBuilder(); 96 } 97 lock (this) 98 { 99 logSource.AppendFormat("{0} ", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss,fff")); 100 logSource.AppendFormat(format, args); 101 logSource.Append(System.Environment.NewLine); 102 } 103 } 104 catch { } 105 } 106 public void SaveLogToFile() 107 { 108 try 109 { 110 string logInfo = GetLogText(); 111 if (logInfo.Length > 0) 112 { 113 System.IO.FileStream fs = GetFileStream(); 114 byte[] buffer = System.Text.UTF8Encoding.UTF8.GetBytes(logInfo); 115 long lockBegin = fs.Length; 116 long lockEnd = buffer.Length; 117 fs.Position = lockBegin; 118 fs.Lock(lockBegin, lockEnd); 119 //fs.WriteAsync(buffer, 0, buffer.Length); 120 fs.Write(buffer, 0, buffer.Length); 121 fs.Unlock(lockBegin, lockEnd); 122 fs.Flush(); 123 //fs.Close(); 124 } 125 } 126 catch { } 127 } 128 public void ClearLogFile() 129 { 130 if ((DateTime.Now - logFileScanLastTime).TotalMinutes < 5) 131 { 132 return; 133 } 134 logFileScanLastTime = DateTime.Now; 135 System.IO.DirectoryInfo directoryInfo = new System.IO.DirectoryInfo(Factory.logsDirPath); 136 if (!directoryInfo.Exists) 137 { 138 return; 139 } 140 System.IO.FileInfo[] files = directoryInfo.GetFiles(this.logFilePre + "*.log", System.IO.SearchOption.TopDirectoryOnly); 141 if (files == null || files.Length < 1) 142 { 143 return; 144 } 145 DateTime time = DateTime.Now.AddDays(0 - logFileRetentionDays); 146 foreach (System.IO.FileInfo file in files) 147 { 148 try 149 { 150 if (file.CreationTime < time) 151 { 152 file.Delete(); 153 } 154 } 155 catch { } 156 } 157 } 158 159 160 } 161 }Log.cs
1 namespace Logger.Core 2 { 3 public class TextFileReader 4 { 5 bool _readStart = false; 6 bool _readEnd = false; 7 System.IO.FileStream _stream = null; 8 System.Text.Encoding _code = null; 9 long _fileLength = 0; 10 long _currentPosition = 0; 11 string _readStr = string.Empty; 12 int _readBytes = 1024; 13 string _filePath = ""; 14 readonly string[] _defaultOffsetStrArray = new string[] { System.Environment.NewLine, " ", ",", ".", "!", "?", ";", ",", "。", "!", "?", ";" }; 15 string[] _offsetStrArray = null; 16 17 public string ReadStr { 18 get { return _readStr; } 19 } 20 public string FilePath { 21 get { return _filePath; } 22 set { _filePath = value; } 23 } 24 public int ReadBytes { 25 get { return _readBytes < 1024 ? 1024 : _readBytes; } 26 set { _readBytes = value; } 27 } 28 public string[] OffsetStrArray { 29 get { return (_offsetStrArray == null|| _offsetStrArray.Length < 1)? _defaultOffsetStrArray : _offsetStrArray; } 30 set { _offsetStrArray = value; } 31 } 32 33 34 public TextFileReader() { 35 _offsetStrArray = _defaultOffsetStrArray; 36 } 37 public TextFileReader(string FilePath) 38 { 39 this.FilePath = FilePath; 40 _offsetStrArray = _defaultOffsetStrArray; 41 } 42 private int GetPosition(string readStr, string[] offsetStrArray) 43 { 44 int position = -1; 45 for (int i = 0; i < offsetStrArray.Length; i++) 46 { 47 position = readStr.LastIndexOf(offsetStrArray[i]); 48 if (position > 0) 49 { 50 break; 51 } 52 } 53 return position; 54 } 55 public bool Read() 56 { 57 if (!_readStart) 58 { 59 //System.IO.FileShare.ReadWrite:允許其它程式讀寫檔案(重要,否則很可能會與負責寫入的程式衝突而被拒絕訪問) 60 _stream = new System.IO.FileStream(this.FilePath, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite); 61 _code = GetType(this.FilePath); 62 _currentPosition = 0; 63 _fileLength = _stream.Length; 64 _readStart = true; 65 } 66 if (_currentPosition < _fileLength) 67 { 68 byte[] readBuffer = new byte[this.ReadBytes]; 69 //設定讀取位置 70 _stream.Seek(_currentPosition, System.IO.SeekOrigin.Begin); 71 //本次實際讀到的位元組數 72 int currentReadBytes = _stream.Read(readBuffer, 0, readBuffer.Length); 73 //讀取位置偏移 74 _currentPosition += currentReadBytes; 75 76 //實際讀到的位元組少於指定的位元組數(在讀到最後一批時) 77 if (currentReadBytes < _readBytes) 78 { 79 byte[] temp = new byte[currentReadBytes]; 80 int index = 0; 81 while (index < currentReadBytes) 82 { 83 temp[index] = readBuffer[index]; 84 index++; 85 } 86 readBuffer = temp; 87 } 88 _readStr = _code.GetString(readBuffer); 89 //如果沒有讀到最後一個位元組則計算位置偏移 90 if (_currentPosition < _fileLength) 91 { 92 int offsetStrPosition = GetPosition(_readStr, this.OffsetStrArray); 93 if (offsetStrPosition > 0)//找到內容則計算位置偏移 94 { 95 //提取將被移除的內容 96 string removeStr = _readStr.Substring(offsetStrPosition + 1); 97 //移除內容 98 _readStr = _readStr.Remove(offsetStrPosition + 1); 99 //位置後退 100 _currentPosition = _currentPosition - _code.GetBytes(removeStr).Length; 101 } 102 } 103 } 104 else 105 { 106 _readEnd = true; 107 _stream.Dispose(); 108 } 109 return !_readEnd; 110 } 111 112 113 public static System.Text.Encoding GetType(string fullname) 114 { 115 System.IO.FileStream fs = new System.IO.FileStream(fullname, System.IO.FileMode.Open, System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite); 116 System.Text.Encoding r = GetType(fs); 117 fs.Close(); 118 return r; 119 } 120 public static System.Text.Encoding GetType(System.IO.FileStream fs) 121 { 122 byte[] Unicode = new byte[] { 0xFF, 0xFE, 0x41 }; 123 byte[] UnicodeBIG = new byte[] { 0xFE, 0xFF, 0x00 }; 124 byte[] UTF8 = new byte[] { 0xEF, 0xBB, 0xBF }; 125 System.Text.Encoding reVal = System.Text.Encoding.Default; 126 127 System.IO.BinaryReader r = new System.IO.BinaryReader(fs, System.Text.Encoding.Default); 128 int i; 129 int.TryParse(fs.Length.ToString(), out i); 130 byte[] ss = r.ReadBytes(i); 131 if (IsUTF8Bytes(ss) || (ss[0] == 0xEF && ss[1] == 0xBB && ss[2] == 0xBF)) 132 { 133 reVal = System.Text.Encoding.UTF8; 134 } 135 else if (ss[0] == 0xFE && ss[1] == 0xFF && ss[2] == 0x00) 136 { 137 reVal = System.Text.Encoding.BigEndianUnicode; 138 } 139 else if (ss[0] == 0xFF && ss[1] == 0xFE && ss[2] == 0x41) 140 { 141 reVal = System.Text.Encoding.Unicode; 142 } 143 r.Close(); 144 return reVal; 145 } 146 private static bool IsUTF8Bytes(byte[] data) 147 { 148 int charByteCounter = 1; 149 byte curByte; 150 for (int i = 0; i < data.Length; i++) 151 { 152 curByte = data[i]; 153 if (charByteCounter == 1) 154 { 155 if (curByte >= 0x80) 156 { 157 while (((curByte <<= 1) & 0x80) != 0) 158 { 159 charByteCounter++; 160 } 161 if (charByteCounter == 1 || charByteCounter > 6) 162 { 163 return false; 164 } 165 } 166 } 167 else 168 { 169 if ((curByte & 0xC0) != 0x80) 170 { 171 return false; 172 } 173 charByteCounter--; 174 } 175 } 176 if (charByteCounter > 1) 177 { 178 throw new Exception("非預期的byte格式"); 179 } 180 return true; 181 } 182 183 184 } 185 }TextFileReader.cs
1 namespace Logger.Core 2 { 3 public static class Factory 4 { 5 private static object setFileThreadCreateLockObj = new object(); 6 private static object loggerCreateLockObj = new object(); 7 public static readonly string logsDirPath = System.IO.Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "logs"); 8 internal static readonly System.Collections.Generic.Dictionary<string, ILog> loggerDic = new System.Collections.Generic.Dictionary<string, ILog>(); 9 internal static System.Threading.Thread setFileThread = null; 10 internal static void SetFileThreadStartFunc(object obj) 11 { 12 while (true) 13 { 14 try 15 { 16 foreach (string key in loggerDic.Keys) 17 { 18 loggerDic[key].SaveLogToFile(); 19 loggerDic[key].ClearLogFile(); 20 } 21 System.Threading.Thread.Sleep(1); 22 } 23 catch { } 24 } 25 } 26 public static void SetFileThreadStart() 27 { 28 if (setFileThread == null) 29 { 30 lock (setFileThreadCreateLockObj) 31 { 32 if (setFileThread == null) 33 { 34 setFileThread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(SetFileThreadStartFunc)); 35 setFileThread.IsBackground = true; 36 setFileThread.Start(null); 37 } 38 } 39 } 40 } 41 public static ILog GetLogger() 42 { 43 return GetLogger("Trace"); 44 } 45 public static ILog GetLogger(string LogFilePre) 46 { 47 return GetLogger(LogFilePre, 90); 48 } 49 public static ILog GetLogger(string logFilePre, int logFileRetentionDays) 50 { 51 logFilePre = GetLogFilePre(logFilePre); 52 if (loggerDic.ContainsKey(logFilePre)) 53 { 54 return loggerDic[logFilePre]; 55 } 56 else 57 { 58 lock (loggerCreateLockObj) 59 { 60 if (loggerDic.ContainsKey(logFilePre)) 61 { 62 return loggerDic[logFilePre]; 63 } 64 else 65 { 66 ILog _logger = new Log(logFilePre, logFileRetentionDays); 67 loggerDic.Add(logFilePre, _logger); 68 return _logger; 69 } 70 } 71 } 72 } 73 public static string GetLogFilePre(string logFilePre) 74 { 75 if (string.IsNullOrEmpty(logFilePre)) 76 77 { 78 logFilePre = "Trace"; 79 } 80 logFilePre = logFilePre.ToLower(); 81 if (!logFilePre.EndsWith("-")) 82 { 83 logFilePre = logFilePre + "-"; 84 } 85 logFilePre = logFilePre.Substring(0, 1).ToUpper() + logFilePre.Substring(1); 86 return logFilePre; 87 } 88 public static System.Collections.Generic.List<string> GetLogFilePreList() 89 { 90 System.Collections.Generic.List<string> reval = new System.Collections.Generic.List<string>(); 91 foreach(string key in loggerDic.Keys) 92 { 93 reval.Add(key); 94 } 95 return reval; 96 } 97 98 } 99 }Factory.cs
以上是實現日誌功能的核心程式碼,下面在類庫專案下直接新增兩個靜態類:
- LogWriter.cs 負責寫,定義了常規的 Fatal , Error , Info , Debug 等方法及預設的日誌時效期
- LogReader.cs 負責讀,如獲取日誌型別列表,獲取日誌檔案列表,或取日誌檔案的TextFileReader物件等
1 namespace Logger 2 { 3 public static class LogWriter 4 { 5 public static Core.ILog Debug() 6 { 7 return Core.Factory.GetLogger("Debug", 3); 8 } 9 public static Core.ILog Debug(string logInfo) 10 { 11 Core.ILog logger = Debug(); 12 logger.Write(logInfo); 13 return logger; 14 } 15 public static Core.ILog Debug(string format, params object[] args) 16 { 17 Core.ILog logger = Debug(); 18 logger.WriteFormat(format, args); 19 return logger; 20 } 21 public static Core.ILog Info() 22 { 23 return Core.Factory.GetLogger("Info", 60); 24 } 25 public static Core.ILog Info(string logInfo) 26 { 27 Core.ILog logger = Info(); 28 logger.Write(logInfo); 29 return logger; 30 } 31 public static Core.ILog Info(string format, params object[] args) 32 { 33 Core.ILog logger = Info(); 34 logger.WriteFormat(format, args); 35 return logger; 36 } 37 public static Core.ILog Error() 38 { 39 return Core.Factory.GetLogger("Error", 60); 40 } 41 public static Core.ILog Error(string logInfo) 42 { 43 Core.ILog logger = Error(); 44 logger.Write(logInfo); 45 return logger; 46 } 47 public static Core.ILog Error(string format, params object[] args) 48 { 49 Core.ILog logger = Error(); 50 logger.WriteFormat(format, args); 51 return logger; 52 } 53 public static Core.ILog Fatal() 54 { 55 return Core.Factory.GetLogger("Fatal", 60); 56 } 57 public static Core.ILog Fatal(string logInfo) 58 { 59 Core.ILog logger = Fatal(); 60 logger.Write(logInfo); 61 return logger; 62 } 63 public static Core.ILog Fatal(string format, params object[] args) 64 { 65 Core.ILog logger = Fatal(); 66 logger.WriteFormat(format, args); 67 return logger; 68 } 69 } 70 }LogWriter.cs
1 namespace Logger 2 { 3 public static class LogReader 4 { 5 public static System.Collections.Generic.List<string> GetLogFilePreList() 6 { 7 return Core.Factory.GetLogFilePreList(); 8 } 9 public static System.IO.FileInfo[] GetLogFiles(string logFilePre) 10 { 11 System.IO.DirectoryInfo dir = new System.IO.DirectoryInfo(Core.Factory.logsDirPath); 12 if (!dir.Exists) 13 { 14 return new System.IO.FileInfo[] { }; 15 } 16 logFilePre = Core.Factory.GetLogFilePre(logFilePre); 17 System.IO.FileInfo[] fis = dir.GetFiles(logFilePre + "*.log", System.IO.SearchOption.TopDirectoryOnly); 18 if (fis == null) 19 { 20 fis = new System.IO.FileInfo[] { }; 21 } 22 return fis; 23 } 24 public static Core.TextFileReader GetTextFileReader(System.IO.FileInfo logFileInfo) 25 { 26 Core.TextFileReader textFileReader = new Core.TextFileReader(logFileInfo.FullName); 27 textFileReader.ReadBytes = 1024 * 1024 * 2; 28 return textFileReader; 29 } 30 } 31 }LogReader
新建一個控制檯程式來測試一下,測試程式碼:
1 class Program 2 { 3 static void Main(string[] args) 4 { 5 Writer(); 6 Reader(); 7 } 8 static void Writer() 9 { 10 for (var i = 1; i < 6; i++) 11 { 12 System.Threading.Thread thread = new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(WriterFunc)); 13 thread.IsBackground = true; 14 thread.Start(i); 15 } 16 } 17 static void WriterFunc(object num) 18 { 19 int threadNum = (int)num; 20 while (true) 21 { 22 Logger.LogWriter.Info("這是執行緒{0}", threadNum); 23 Logger.LogWriter.Error("這是執行緒{0}", threadNum); 24 Logger.LogWriter.Fatal("這是執行緒{0}", threadNum); 25 System.Threading.Thread.Sleep(10); 26 } 27 } 28 static void Reader() 29 { 30 string cmd = ""; 31 while (cmd != "r") 32 { 33 Console.Write("輸入 r 讀取日誌:"); 34 cmd = Console.ReadLine(); 35 } 36 37 System.Collections.Generic.List<string> preList = Logger.LogReader.GetLogFilePreList(); 38 if (preList.Count < 1) 39 { 40 Console.ForegroundColor = ConsoleColor.Red; 41 Console.WriteLine("未能檢索到日誌記錄!"); 42 Console.ResetColor(); 43 Reader(); 44 } 45 Console.WriteLine("-----------------------------------------------------------"); 46 47 Console.WriteLine("編號\t型別字首"); 48 Console.ForegroundColor = ConsoleColor.Red; 49 for (var i = 0; i < preList.Count; i++) 50 { 51 Console.WriteLine("{0}\t{1}", i + 1, preList[i]+"*"); 52 } 53 Console.ResetColor(); 54 Console.WriteLine("-----------------------------------------------------------"); 55 56 Console.Write("輸入編號讀取日誌檔案列表:"); 57 int preNum = GetInputNum(1, preList.Count); 58 59 var files = Logger.LogReader.GetLogFiles(preList[preNum-1]); 60 if (files.Length < 1) 61 { 62 Console.ForegroundColor = ConsoleColor.Red; 63 Console.WriteLine("未能檢索到日誌檔案!"); 64 Console.ResetColor(); 65 Reader(); 66 } 67 Console.WriteLine("-----------------------------------------------------------"); 68 69 Console.WriteLine("編號\t日誌檔案"); 70 Console.ForegroundColor = ConsoleColor.Red; 71 for (var i = 0; i < files.Length; i++) 72 { 73 Console.WriteLine("{0}\t{1}", i + 1, System.IO.Path.GetFileName(files[i].FullName)); 74 } 75 Console.ResetColor(); 76 Console.WriteLine("-----------------------------------------------------------"); 77 78 Console.Write("輸入編號讀取日誌:"); 79 int fileNum = GetInputNum(1, files.Length); 80 Console.WriteLine("-----------------------------------------------------------"); 81 82 var reader = Logger.LogReader.GetTextFileReader(files[fileNum - 1]); 83 while (reader.Read()) 84 { 85 Console.Write(reader.ReadStr); 86 } 87 88 Console.WriteLine(); 89 90 Reader(); 91 92 } 93 static int GetInputNum(int min, int max) 94 { 95 int num = -1; 96 while (true) 97 { 98 string inputNum = Console.ReadLine(); 99 bool flag = false; 100 if (System.Text.RegularExpressions.Regex.IsMatch(inputNum, @"^\d{1,9}$")) 101 { 102 num = Convert.ToInt32(inputNum); 103 flag = num <= max && num >= min; 104 } 105 if (!flag) 106 { 107 Console.Write("輸入不合法,請重新輸入:"); 108 num = -1; 109 } 110 else 111 { 112 break; 113 } 114 } 115 return num; 116 } 117 }Program.cs
程式執行截圖:
至此,一個日誌類庫就算完成了。
鑑於個人水平問題,不敢妄言更高效或更優雅,但是可以整合到其它專案中工作了,該程式碼作者在公司的實際專案中有使用。
不用各種繁雜的配置,想寫就寫,如果想要新增一個其它型別的日誌只要在LogWriter.cs中增加方法即可。
(^_^)大神莫笑,小菜莫怕,歡迎善意的溝通和交流!