當前位置: 妍妍網 > 碼農

【BeetleX重構】日誌規範設計和實作

2024-06-06碼農

為什麽要在編寫元件之前就要做好日誌規範的設計和定義呢?那我還是來一段GPT為我們解答的內容和日誌規範設計的一些基礎。 設計一個良好的程式日誌規範對C#計畫的維護和偵錯非常重要。 以下是一個C#程式日誌規範的建議,包括日誌的級別、格式、內容和使用註意事項。

一、日誌級別

  1. Trace :最詳細的資訊,通常只在開發期間啟用,用於記錄程式的所有細節。

  2. Debug :偵錯資訊,開發和測試期間使用,記錄較為詳細的偵錯資訊。

  3. Info :關鍵路徑的資訊,用於記錄程式的正常操作。

  4. Warn :警告資訊,程式可以繼續執行但可能會有問題。

  5. Error :錯誤資訊,程式發生錯誤但可以繼續執行。

  6. Fatal :嚴重錯誤資訊,程式發生嚴重錯誤需要立即終止。

二、日誌格式

建議使用標準的日誌格式,便於解析和分析。範例如下:

[時間戳][日誌級別][執行緒ID][類名.方法名]- 日誌訊息

三、日誌內容

  1. 時間戳 :記錄日誌生成的時間,精確到毫秒。例如: 2024-06-06 12:34:56.789

  2. 日誌級別 :日誌的嚴重性級別,例如: INFO ERROR

  3. 執行緒ID :記錄生成日誌的執行緒ID,便於並行環境下的偵錯

  4. 類名.方法名 :記錄生成日誌的類名和方法名,便於定位程式碼

  5. 日誌訊息 :具體的日誌資訊,詳細描述發生的事件

接下來講解BeetleX是怎樣去實作這個規範了,首先元件定義了一個日誌記錄介面

publicinterfaceILogHandler{voidWriteLog(LogLevel level, int threadid, string location, string model, string tag, string message, string stackTrace);}

一個非常簡單寫日誌的方法,這個方法似乎使用起來也比較麻煩...那就簡化一下它,定義一個結構來簡化一些參數

publicstruct LogWriter{public LogLevel Level { get; set; }public ILogHandler Loger { get; set; }publicvoidWrite(EndPoint location, string model, string tag, string message) { Write(location?.ToString(), model, tag, message, null); }publicvoidWriteException(EndPoint location, string model, string tag, Exception e_) { Write(location?.ToString(), model, tag, e_.Message, e_.StackTrace); }publicvoidWrite(Socket location, string model, string tag, string message) { Write(location?.RemoteEndPoint?.ToString(), model, tag, message, null); }publicvoidWriteException(Socket location, string model, string tag, Exception e_) { Write(location?.RemoteEndPoint?.ToString(), model, tag, e_.Message, e_.StackTrace); }publicvoidWrite(ILocation context, string model, string tag, string message) { Write(context?.EndPoint?.ToString(), model, tag, message, null); }publicvoidWriteException(ILocation context, string model, string tag, Exception e_) { Write(context?.EndPoint?.ToString(), model, tag, e_.Message, e_.StackTrace); }publicvoidWrite(string location, string model, string tag, string message, string stackTrace) {if (string.IsNullOrEmpty(location)) location = "BeetleX"; Loger?.WriteLog(Level, Thread.CurrentThread.ManagedThreadId, location, model, tag, message, stackTrace); }}

透過這個結構寫日誌就簡單多了,那這個日誌記錄結構如何獲取呢?那針對它再定義一個介面

publicinterface IGetLogHandler : ILocation{ LogWriter? GetLoger(LogLevel type);}

這介面也只一個方法,根據日誌級別返回上面定義的結構。在需要記錄日誌的類中實作一個這個方法

public LogWriter? GetLoger(LogLevel level){if ((int)(LogLevel) <= (int)level) { LogWriter result = new LogWriter(); result.Level = level; result.Loger = this;return result; }returnnull;}

方法還存在返回null的結構,外面使用每次都要判斷一下似乎很麻煩!別忘記C#有先進的語法系統,看一下是如何使用的

publicoverridevoidWrite(byte[] buffer, int offset, int count){base.Write(buffer, offset, count); LogHandler?.GetLoger(LogLevel.Debug)?.Write(LogHandler, "BXSslStream", "SyncData", $"Write length {count}"); LogHandler?.GetLoger(Logs.LogLevel.Trace)?.Write(LogHandler, "BXSslStream", "✉ SyncData", $"Write {Convert.ToHexString(buffer, offset, count)}");}

用起來是不是很方便?比起多層判斷再呼叫省事很多,一行程式碼即可。有了記錄日誌,那自然需要設計一個保存日誌的。針對保存也設計一個介面

publicinterfaceILogOutputHandler{voidWrite(LogRecord log);voidFlush();}

介面也是很簡單的,一個寫日誌記錄方法和落盤的方法;針對Console實作的一擴充套件如下:

public classLogOutputToConsole : ILogOutputHandler{staticLogOutputToConsole() { Console.OutputEncoding = Encoding.UTF8; }publicLogOutputToConsole() { }static SingleThreadDispatcher<LogRecord> _dispatcher;public SingleThreadDispatcher<LogRecord> Dispatcher {get {if (_dispatcher != null)return _dispatcher;else {lock (typeof(LogOutputToConsole)) {if (_dispatcher == null) _dispatcher = new SingleThreadDispatcher<LogRecord>(OnWriteLog);return _dispatcher; } } } }static Task OnWriteLog(LogRecord e) { Console.Write($"[{DateTime.Now.ToString("HH:mmm:ss")}] ");switch (e.Level) {case LogLevel.Error: Console.ForegroundColor = ConsoleColor.DarkRed;break;case LogLevel.Warring: Console.ForegroundColor = ConsoleColor.Yellow;break;case LogLevel.Fatal: Console.ForegroundColor = ConsoleColor.Red;break;case LogLevel.Info: Console.ForegroundColor = ConsoleColor.Green;break;case LogLevel.Debug: Console.ForegroundColor = ConsoleColor.DarkGray;break;default: Console.ForegroundColor = ConsoleColor.White;break; } Console.Write($"[{e.Level.ToString().PadLeft(7)}]"); Console.ForegroundColor = ConsoleColor.Gray; Console.Write($" {e.ThreadID.ToString().PadLeft(3)} [{e.Location.PadRight(16)}{e.Model}{e.Tag.PadLeft(26 - e.Model.Length)}] {e.Message}");if (!string.IsNullOrEmpty(e.StackTrace)) Console.Write($"\r\n\t\t{e.StackTrace}\r\n");else Console.Write("\r\n");return Task.CompletedTask; }publicvoidWrite(LogRecord log) { Dispatcher.Enqueue(log); }publicvoidFlush() { }}

以上BeetleX的日誌功能就設計實作完成,並沒有依賴於第三方,盡量不依賴第三方元件也是元件開始就已經明確的立場。接下來看一下元件的詳細日誌細化到什麽樣的程度。

疑問 :有人可以會問在這核心元件存在這麽多記錄日誌的程式碼會影響元件的整體效能嗎?其實當Level不符合要求的時候後面記錄日誌方法的呼叫都不會存在,所以單憑這個 Level值判斷是不會對元件有效能上的影響的。當然日誌輸出時刷於大量資訊輸出記錄肯定會影響到整體效能,具體還是根據情況來制定Level的輸出。

BeetleX

開源跨平台通訊框架(支持TLS)

提供HTTP,Websocket,MQTT,Redis,RPC和服務閘道器開源元件

個人微信:henryfan128 QQ:28304340
有豐富的高吐網路服務設計經驗

關註公眾號

https://github.com/beetlex-io/