最近在寫資料庫程式,需要一個高效能的結構化日誌記錄元件,簡單研究了一下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,歡迎感興趣的小夥伴們加入共同完善,當然更歡迎贊助專案或給作者介紹工作(目前找工作中)。