開源高性能結(jié)構(gòu)化日志模塊NanoLog
??最近在寫數(shù)據(jù)庫程序,需要一個高性能的結(jié)構(gòu)化日志記錄組件,簡單研究了一下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($"發(fā)生異常: {nullable}, Msg={stringValue}");
NanoLogger.Stop();
執(zhí)行后控制臺輸出如下圖(記錄的結(jié)構(gòu)化值會高亮顯示):

二、性能測試
??以下測試僅用一個日期類型參數(shù): 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]代碼生成方式
三、實現(xiàn)原理
+-----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自定義實現(xiàn)LogMessageBuilder,一方面避免值類型的裝箱,另一方面可以記錄結(jié)構(gòu)化信息(名稱、類型、值、格式化);
-
啟用則將日志消息、對應的屬性類型及屬性值序列化后寫入LogMessage內(nèi)。這里的序列化非常簡單,僅相當于一個內(nèi)存復制(參考下圖)。LogMessage是一個結(jié)構(gòu)體,如果序列化后的數(shù)據(jù)小于閥值則直接存儲在內(nèi)置的緩沖塊內(nèi)(沒有Heap內(nèi)存分配的問題),否則從ArrayPool
內(nèi)租用一個緩沖塊存儲超出部分;
+--------------------LogMessage 緩沖塊-----------------------+
|-TokenType-|---Value---|-TokenType-|--------Value------|---|
| Literal | 5,"Hello" | Int | "name",12345,"X2" | |
+-----------------------------------------------------------+
-
序列化后的事件信息(LogEvent)及消息數(shù)據(jù)(LogMessage)直接加入一個多生產(chǎn)者-單消費者的消息隊列,至此前端日志記錄過程結(jié)束,不阻塞后續(xù)代碼執(zhí)行;
-
后臺線程循環(huán)從隊列取出待處理日志,由配置的ILogger實現(xiàn)處理。例如ConsoleLogger格式化后輸出至控制臺;FileLogger將數(shù)據(jù)寫入文件存儲。
四、日志搜索
??結(jié)構(gòu)化日志當然得支持結(jié)構(gòu)化搜索,參考控制臺工程NanoLog.File.Viewer使用Roslyn解析字符串表達式編譯后過濾日志記錄(參考下圖):
- 表達式中
e.XXX對應LogEvent的相關屬性條件; - 表達式中
e["xxx"]["yyy"]對應LogMessage結(jié)構(gòu)化記錄的值條件。

五、本文小結(jié)
??最后GitHub地址:https://github.com/enjoycode/NanoLog.git, 作者個人能力實在有限Bug在所難免,如有問題請郵件聯(lián)系或Github Issue,歡迎感興趣的小伙伴們加入共同完善,當然更歡迎贊助項目或給作者介紹工作(目前找工作中)。

浙公網(wǎng)安備 33010602011771號