动手造轮子:实现一个简单的基于 Console 的日志输出
動手造輪子:實現一個簡單的基于 Console 的日志輸出
Intro
之前結合了微軟的 Logging 框架和 Serilog 寫了一個簡單的日志框架,但是之前的用法都是基于 log4net、serilog 的,沒有真正自己實現一個日志輸出,比如 Console、文件、數據庫、ES等,關于日志框架的設計可以參考之前的文章?動手造輪子:寫一個日志框架
實現思路
把日志放在一個隊列中,通過隊列方式慢慢的寫,避免并發問題,同時異步寫到 Console 避免因為寫日志阻塞主線程的執行
輸出的格式如何定義呢,像 log4net/nlog/serilog 這些都會支持自定義日志輸出格式,所以我們可以設計一個接口,實現一個默認日志格式,當用戶自定義日志格式的時候就使用用戶自定義的日志格式
針對不同的日志級別的日志應該使用不同的顏色來輸出以方便尋找不同級別的日志
使用示例
來看一個使用的示例:
LogHelper.ConfigureLogging(builder?=> {builder.AddConsole()//.AddLog4Net()//.AddSerilog(loggerConfig?=>?loggerConfig.WriteTo.Console())//.WithMinimumLevel(LogHelperLogLevel.Info)//.WithFilter((category,?level)?=>?level?>?LogHelperLogLevel.Error?&&?category.StartsWith("System"))//.EnrichWithProperty("Entry0",?ApplicationHelper.ApplicationName)//.EnrichWithProperty("Entry1",?ApplicationHelper.ApplicationName,?e?=>?e.LogLevel?>=?LogHelperLogLevel.Error); });var?abc?=?"1233"; var?logger?=?LogHelper.GetLogger<LoggerTest>(); logger.Debug("12333?{abc}",?abc); logger.Trace("122334334"); logger.Info($"122334334?{abc}");logger.Warn("12333,?err:{err}",?"hahaha"); logger.Error("122334334"); logger.Fatal("12333");日志輸出如下:
log output默認的日志格式是 JSON 字符串,因為我覺得 JSON 更加結構化,也會比較方便的去 PATCH 和日志分析,微軟的 Logging 框架也是在 .NET 5.0 中加入了 JsonConsoleFormatter,可以直接輸出 JSON 到控制臺,如果需要也可以自定義一個 Formatter 來實現自定義的格式化
實現源碼
使用 IConsoleLogFormatter 接口來自定義日志格式化
public?interface?IConsoleLogFormatter {string?FormatAsString(LogHelperLoggingEvent?loggingEvent); }internal?sealed?class?DefaultConsoleLogFormatter?:?IConsoleLogFormatter {private?static?readonly?JsonSerializerSettings?_serializerSettings?=?new(){Converters?={new?StringEnumConverter()},ReferenceLoopHandling?=?ReferenceLoopHandling.Ignore,};public?string?FormatAsString(LogHelperLoggingEvent?loggingEvent){return?loggingEvent.ToJson(_serializerSettings);} }實現的代碼比較簡單,隊列的話使用了 BlockingCollection 來實現了一個內存中的隊列
ConsoleLoggingProvider實現如下:
internal?sealed?class?ConsoleLoggingProvider?:?ILogHelperProvider {private?readonly?IConsoleLogFormatter?_formatter;private?readonly?BlockingCollection<LogHelperLoggingEvent>?_messageQueue?=?new();private?readonly?Thread?_outputThread;public?ConsoleLoggingProvider(IConsoleLogFormatter?formatter){_formatter?=?formatter;//?Start?Console?message?queue?processor_outputThread?=?new?Thread(ProcessLogQueue){IsBackground?=?true,Name?=?"Console?logger?queue?processing?thread"};_outputThread.Start();}public?void?EnqueueMessage(LogHelperLoggingEvent?message){if?(!_messageQueue.IsAddingCompleted){try{_messageQueue.Add(message);return;}catch?(InvalidOperationException)?{?}}//?Adding?is?completed?so?just?log?the?messagetry{WriteLoggingEvent(message);}catch?(Exception){//?ignored}}public?void?Log(LogHelperLoggingEvent?loggingEvent){EnqueueMessage(loggingEvent);}private?void?ProcessLogQueue(){try{foreach?(LogHelperLoggingEvent?message?in?_messageQueue.GetConsumingEnumerable()){WriteLoggingEvent(message);}}catch{try{_messageQueue.CompleteAdding();}catch{//?ignored}}}private?void?WriteLoggingEvent(LogHelperLoggingEvent?loggingEvent){try{var?originalColor?=?Console.ForegroundColor;try{var?log?=?_formatter.FormatAsString(loggingEvent);var?logLevelColor?=?GetLogLevelConsoleColor(loggingEvent.LogLevel);Console.ForegroundColor?=?logLevelColor.GetValueOrDefault(originalColor);if?(loggingEvent.LogLevel?==?LogHelperLogLevel.Error||?loggingEvent.LogLevel?==?LogHelperLogLevel.Fatal){Console.Error.WriteLine(log);}else{Console.WriteLine(log);}}catch?(Exception?ex){Console.WriteLine(ex);}finally{Console.ForegroundColor?=?originalColor;}}catch{Console.WriteLine(loggingEvent.ToJson());}}private?static?ConsoleColor??GetLogLevelConsoleColor(LogHelperLogLevel?logLevel){return?logLevel?switch{LogHelperLogLevel.Trace?=>?ConsoleColor.Gray,LogHelperLogLevel.Debug?=>?ConsoleColor.Gray,LogHelperLogLevel.Info?=>?ConsoleColor.DarkGreen,LogHelperLogLevel.Warn?=>?ConsoleColor.Yellow,LogHelperLogLevel.Error?=>?ConsoleColor.Red,LogHelperLogLevel.Fatal?=>?ConsoleColor.DarkRed,_?=>?null};} }為了方便使用和更好的訪問控制,上面的 ConsoleLoggingProvider 聲明成了 internal 并不直接對外開放,并且定義了下面的擴展方法來使用:
public?static?ILogHelperLoggingBuilder?AddConsole(this?ILogHelperLoggingBuilder?loggingBuilder,?IConsoleLogFormatter??consoleLogFormatter?=?null) {loggingBuilder.AddProvider(new?ConsoleLoggingProvider(consoleLogFormatter????new?DefaultConsoleLogFormatter()));return?loggingBuilder; }DelegateFormatter
需要自定義的 Console 日志的格式的時候就實現一個 IConsoleLogFormatter 來實現自己的格式化邏輯就可以了,不想手寫一個類?也可以實現一個 Func<LogHelperLoggingEvent, string> 委托,內部會把委托轉成一個 IConsoleLogFormatter,實現如下:
internal?sealed?class?DelegateConsoleLogFormatter?:?IConsoleLogFormatter {private?readonly?Func<LogHelperLoggingEvent,?string>?_formatter;public?DelegateConsoleLogFormatter(Func<LogHelperLoggingEvent,?string>?formatter){_formatter?=?formatter????throw?new?ArgumentNullException(nameof(formatter));}public?string?FormatAsString(LogHelperLoggingEvent?loggingEvent)?=>?_formatter(loggingEvent); }擴展方法:
public?static?ILogHelperLoggingBuilder?AddConsole(this?ILogHelperLoggingBuilder?loggingBuilder,?Func<LogHelperLoggingEvent,?string>?formatter) {loggingBuilder.AddProvider(new?ConsoleLoggingProvider(new?DelegateConsoleLogFormatter(formatter)));return?loggingBuilder; }More
在寫一些小應用的時候,經常會遇到這樣的場景,就是執行一個方法的時候包一層 try...catch,在發生異常時輸出異常信息,稍微包裝了一個
public?static?Action<Exception>??OnInvokeException?{?get;?set;?}public?static?void?TryInvoke(Action?action) {Guard.NotNull(action,?nameof(action));try{action();}catch?(Exception?ex){OnInvokeException?.Invoke(ex);} }原來想突出顯示錯誤信息的時候,我會特別設置一個 Console 的顏色以便方便的查看,原來會這樣設置,之前的 gRPC 示例項目原來就是這樣做的:
InvokeHelper.OnInvokeException?=?ex?=> {var?originalColor?=?ForegroundColor;ForegroundColor?=?ConsoleColor.Red;WriteLine(ex);ForegroundColor?=?originalColor; };有了 Console logging 之后,我就可以把上面的委托默認設置為 Log 一個 Error(OnInvokeException = ex => LogHelper.GetLogger(typeof(InvokeHelper)).Error(ex);),只需要配置 Logging 使用 Console 輸出就可以了,也可以設置日志級別忽略一些不太需要的日志
LogHelper.ConfigureLogging(x=>x.AddConsole().WithMinimumLevel(LogHelperLogLevel.Info)); diffReferences
https://github.com/WeihanLi/WeihanLi.Common
https://github.com/WeihanLi/WeihanLi.Common/blob/dev/src/WeihanLi.Common/Logging/ConsoleLoggingProvider.cs
總結
以上是生活随笔為你收集整理的动手造轮子:实现一个简单的基于 Console 的日志输出的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 聊聊自驱团队的构建(四)
- 下一篇: 自定义验证规则ValidationAtt