開源高效能結構化日誌模組NanoLog

白菜园發表於2024-06-11

  最近在寫資料庫程式,需要一個高效能的結構化日誌記錄元件,簡單研究了一下Microsoft.Extensions.Logging和Serilog,還是決定重造一個輪子。

一、使用方法

  直接參考以下示例程式碼:

NanoLogger.Start();

DateTime? nullable = null;
const bool boolValue = true;
const char charValue = 'C';
const int intValue1 = 12345;
const int intValue2 = 0xABCDEF;
const string stringValue = "你好世界";
var point = new Point { X = 123, Y = 456 };
var person = new Person { Name = "Rick", Birthday = new DateTime(1977, 3, 1), Phone = "13861838709" };

var log = new NanoLogger();

log.Trace("Trace message");
log.Trace($"Trace {DateTime.Now}, {intValue1}, 0x{intValue2:X}");
log.Debug($"Debug {DateTime.Now:yyyy-MM-dd hh:mm:ss}, 你好世界!");
log.Info($"Info {point}, {person}, {charValue}");
log.Warn($"這是警告: {boolValue}");
log.Error($"發生異常: {nullable}, Msg={stringValue}");

NanoLogger.Stop();

執行後控制檯輸出如下圖(記錄的結構化值會高亮顯示):

二、效能測試

  以下測試僅用一個日期型別引數: nanoLogger.Info($"Hello World {now}");

Method Mean Error StdDev Ratio Lock Contentions Allocated Alloc Ratio
NanoLog 154.6 ns 0.91 ns 3.48 ns 0.04 - - 0.00
MsLog 3,922.2 ns 49.13 ns 202.60 ns 1.00 0.0004 264 B 1.00
MsLogCodeGen 4,079.3 ns 52.49 ns 218.77 ns 1.04 0.0010 208 B 0.79
  • NanoLog 本元件控制檯輸出
  • MsLog Microsoft.Extensions.Logging控制檯輸出
  • MsLogCodeGen 使用[LoggerMessageAttribute]程式碼生成方式

三、實現原理


+-----Logger Threads-----+                               +-----Background Thread----+
|                        |                               |                          |
|   logger.Info(xxx)     |                               |    ConsoleLogger.Log()   |
|                        |      +-----Log Queue---+      |                          |
|   logger.Debug(xxx)    |  ==> |-Log-|-Log-|-...-| ==>  |    FileLogger.Log()      |
|                        |      +-----------------+      |                          |
|   logger.Warn(xxx)     |                               |    OtherLogger.Log()     |
|                        |                               |                          |
+------------------------+                               +--------------------------+
  • 日誌記錄時先判斷對應的日誌級別是否啟用,不啟用直接忽略。這裡使用C# 6的InterpolatedStringHandlerAttribute自定義實現LogMessageBuilder,一方面避免值型別的裝箱,另一方面可以記錄結構化資訊(名稱、型別、值、格式化);

  • 啟用則將日誌訊息、對應的屬性型別及屬性值序列化後寫入LogMessage內。這裡的序列化非常簡單,僅相當於一個記憶體複製(參考下圖)。LogMessage是一個結構體,如果序列化後的資料小於閥值則直接儲存在內建的緩衝塊內(沒有Heap記憶體分配的問題),否則從ArrayPool內租用一個緩衝塊儲存超出部分;

+--------------------LogMessage 緩衝塊-----------------------+
|-TokenType-|---Value---|-TokenType-|--------Value------|---|
|  Literal  | 5,"Hello" |   Int     | "name",12345,"X2" |   |
+-----------------------------------------------------------+
  • 序列化後的事件資訊(LogEvent)及訊息資料(LogMessage)直接加入一個多生產者-單消費者的訊息佇列,至此前端日誌記錄過程結束,不阻塞後續程式碼執行;

  • 後臺執行緒迴圈從佇列取出待處理日誌,由配置的ILogger實現處理。例如ConsoleLogger格式化後輸出至控制檯;FileLogger將資料寫入檔案儲存。

四、日誌搜尋

  結構化日誌當然得支援結構化搜尋,參考控制檯工程NanoLog.File.Viewer使用Roslyn解析字串表示式編譯後過濾日誌記錄(參考下圖):

  • 表示式中e.XXX對應LogEvent的相關屬性條件;
  • 表示式中e["xxx"]["yyy"]對應LogMessage結構化記錄的值條件。

五、本文小結

  最後GitHub地址:https://github.com/enjoycode/NanoLog.git, 作者個人能力實在有限Bug在所難免,如有問題請郵件聯絡或Github Issue,歡迎感興趣的小夥伴們加入共同完善,當然更歡迎贊助專案或給作者介紹工作(目前找工作中)。

相關文章