跟我一起学.NetCore之日志(Log)模型核心
前言
魯迅都說:沒有日志的系統不能上線(魯迅說:這句我沒說過,但是在理)!日志對于一個系統而言,特別重要,不管是用于事務審計,還是用于系統排錯,還是用于安全追蹤.....都扮演了很重要的角色;之前有很多第三方的日志框架也很給力,如Log4Net、NLog和Serilog等,在.NetCore中也集成了日志模型,使用便捷,同時很方便的與第三方日志框架進行集成擴展;
正文
實例演示之前,先了解一下日志級別,后續如果不想輸出全部日志,可以通過日志級別進行過濾,同時通過日志級別可以標注日志內容的重要程度:
來一個控制臺程序實例演示:
運行結果:
咋樣,使用還是依舊簡單,這里是控制臺程序,還需要寫配置框架和依賴注入相關的代碼邏輯,如果在WebAPI項目,直接就可以使用日志記錄了,如下:
對于WebAPI項目而言,在項目啟動流程分析的時候,就提到內部已經注冊了相關服務了,所以才能這樣如此簡單的使用;
難道日志就這樣結束了嗎?猜想看到這的小伙伴也不甘心,是的,得進一步了解,不需要特別深入,但至少得知道關鍵嘛,對不對?
老規矩,程序中能看到日志相關點,當然就從這開始,看看是如何注冊日志啊相關服務的:
對應代碼:
namespace Microsoft.Extensions.DependencyInjection {//?IServiceCollection的擴展方法,用于注冊日志相關服務public static class LoggingServiceCollectionExtensions{public static IServiceCollection AddLogging(this IServiceCollection services){return services.AddLogging(delegate{});}//?核心方法,上面的方法就是調用下面這個public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure){if (services == null){throw new ArgumentNullException("services");}//?為了支持Options選項,得注冊Options相關服務,上篇講過services.AddOptions();// 注冊ILoggerFactoryservices.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());//?注冊ILoggerservices.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>)));//?注冊日志級別過濾,并默認設置級別為Informationservices.TryAddEnumerable(ServiceDescriptor.Singleton((IConfigureOptions<LoggerFilterOptions>)new DefaultLoggerLevelConfigureOptions(LogLevel.Information)));//?執行傳入的委托方法configure(new LoggingBuilder(services));return services;}} }日志相關服務注冊了解了,那接著看看關鍵實現,其實日志記錄有三個核心類型:ILogger、ILoggerFactory和ILoggerProvider,對應的實現分別是Logger、LoggerFactory、xxxLoggerProvider;
xxxLoggerProvider:針對于不同的目的地創建對應的xxxLogger,這里的xxxLogger負責在目的地(文件、數據庫、控制臺等)寫入內容;
LoggerFactory:負責創建Logger,其中包含所有注冊的xxxLoggerProvider對應Logger;
Logger:以上兩種;
????
扒開這三個類型的定義,簡單看看都定義了什么....
ILogger/Logger
namespace Microsoft.Extensions.Logging {public interface ILogger{//?記錄日志方法,其中包含日志級別、事件ID、寫入的內容、格式化內容等void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);// 判斷對應的日志級別是否可用bool IsEnabled(LogLevel logLevel);//?日志作用域IDisposable BeginScope<TState>(TState state);} }Logger中挑了比較關鍵的屬性和方法簡單說說
internal class Logger : ILogger {//?用于緩存真正Logger記錄器的public LoggerInformation[] Loggers { get; set; }public MessageLogger[] MessageLoggers { get; set; }//?這個用于緩存日志作用域Loggers????public ScopeLogger[] ScopeLoggers { get; set; }// Log日志記錄方法 public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter){var loggers = MessageLoggers;if (loggers == null){return;}List<Exception> exceptions = null;// 遍歷對應的Loggers for (var i = 0; i < loggers.Length; i++){ref readonly var loggerInfo = ref loggers[i];if (!loggerInfo.IsEnabled(logLevel)){continue;}//?執行內部方法LoggerLog(logLevel, eventId, loggerInfo.Logger, exception, formatter, ref exceptions, state);}if (exceptions != null && exceptions.Count > 0){ThrowLoggingError(exceptions);}static void LoggerLog(LogLevel logLevel, EventId eventId, ILogger logger, Exception exception, Func<TState, Exception, string> formatter, ref List<Exception> exceptions, in TState state){try{// 記錄日志內容 logger.Log(logLevel, eventId, state, exception, formatter);}catch (Exception ex){if (exceptions == null){exceptions = new List<Exception>();}exceptions.Add(ex);}}} }ILoggerFactory/LoggerFactory
namespace Microsoft.Extensions.Logging {//?創建?ILogger和注冊LoggerProviderpublic interface ILoggerFactory : IDisposable{//?根據名稱創建ILogger????ILogger CreateLogger(string categoryName);//?注冊ILoggerProvidervoid AddProvider(ILoggerProvider provider);} } ........省略方法-私下研究...... // LoggerFactory挑了幾個關鍵方法進行說明 //?創建Logger public ILogger CreateLogger(string categoryName) {if (CheckDisposed()){throw new ObjectDisposedException(nameof(LoggerFactory));}lock (_sync){if (!_loggers.TryGetValue(categoryName, out var logger)){//?new一個Logger,這是LoggerFactory管理的Logger????????logger = new Logger{//?根據注冊的xxxLoggerProvider創建具體的xxxLogger//?并將其緩存到LoggerFactory創建的Logger對應的Loggers屬性中????????????????????????????Loggers = CreateLoggers(categoryName),};//?根據消息級別和作用域范圍,賦值對應的MessageLoggers、ScopeLoggers(logger.MessageLoggers, logger.ScopeLoggers) = ApplyFilters(logger.Loggers);//?同時將創建出來的logger緩存在字典中_loggers[categoryName] = logger;}return logger;} } // 這個用于注冊具體的xxxLoggerProvider public void AddProvider(ILoggerProvider provider) {if (CheckDisposed()){throw new ObjectDisposedException(nameof(LoggerFactory));}lock (_sync){//?將傳入的provider封裝了結構體進行緩存???AddProviderRegistration(provider, dispose: true);//?同時創建對應的logger,創建過程和上面一樣foreach (var existingLogger in _loggers){var logger = existingLogger.Value;var loggerInformation = logger.Loggers;//?在原來基礎上增加具體的xxxLoggervar newLoggerIndex = loggerInformation.Length;Array.Resize(ref loggerInformation, loggerInformation.Length + 1);loggerInformation[newLoggerIndex] = new LoggerInformation(provider, existingLogger.Key);logger.Loggers = loggerInformation;(logger.MessageLoggers, logger.ScopeLoggers) = ApplyFilters(logger.Loggers);}} } //?封裝對應的xxxLoggerProvider,然后進行緩存 private void AddProviderRegistration(ILoggerProvider provider, bool dispose) {// 先封裝成結構體,然后在緩存,方便后續生命周期管理_providerRegistrations.Add(new ProviderRegistration{Provider = provider,ShouldDispose = dispose});// 判斷是否繼承了ISupportExternalScope if (provider is ISupportExternalScope supportsExternalScope){if (_scopeProvider == null){_scopeProvider = new LoggerExternalScopeProvider();}supportsExternalScope.SetScopeProvider(_scopeProvider);} } //?創建具體的xxxLogger private LoggerInformation[] CreateLoggers(string categoryName) {//?根據注冊的xxxLoggerProvider個數初始化一個數組var loggers = new LoggerInformation[_providerRegistrations.Count];//?遍歷注冊的xxxLoggerProvider,創建具體的xxxLogger???for (var i = 0; i < _providerRegistrations.Count; i++){//?創建具體的xxxLogger????loggers[i] = new LoggerInformation(_providerRegistrations[i].Provider, categoryName);}return loggers; } ........省略方法-私下研究......ILoggerProvider/xxxLoggerProvider
namespace Microsoft.Extensions.Logging {public interface ILoggerProvider : IDisposable{//?根據名稱創建對應的LoggerILogger CreateLogger(string categoryName);} } namespace Microsoft.Extensions.Logging {public interface ILoggerProvider : IDisposable{// 根據名稱創建對應的LoggerILogger CreateLogger(string categoryName);} } namespace Microsoft.Extensions.Logging.Console {[ProviderAlias("Console")]public class ConsoleLoggerProvider : ILoggerProvider, ISupportExternalScope{//?支持Options動態監聽??private readonly IOptionsMonitor<ConsoleLoggerOptions> _options;//?緩存對應的xxxLogger????????private readonly ConcurrentDictionary<string, ConsoleLogger> _loggers;// 日志處理????????private readonly ConsoleLoggerProcessor _messageQueue;private IDisposable _optionsReloadToken;private IExternalScopeProvider _scopeProvider = NullExternalScopeProvider.Instance;// 構造函數,初始化public ConsoleLoggerProvider(IOptionsMonitor<ConsoleLoggerOptions> options){_options = options;_loggers = new ConcurrentDictionary<string, ConsoleLogger>();ReloadLoggerOptions(options.CurrentValue);_optionsReloadToken = _options.OnChange(ReloadLoggerOptions);_messageQueue = new ConsoleLoggerProcessor();// 判斷是否是Windows系統,因為即日至的方式不一樣????????????if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)){ // 如果是windows_messageQueue.Console = new WindowsLogConsole();_messageQueue.ErrorConsole = new WindowsLogConsole(stdErr: true);}else{ // 如果是其他平臺_messageQueue.Console = new AnsiLogConsole(new AnsiSystemConsole());_messageQueue.ErrorConsole = new AnsiLogConsole(new AnsiSystemConsole(stdErr: true));}}private void ReloadLoggerOptions(ConsoleLoggerOptions options){foreach (var logger in _loggers){logger.Value.Options = options;}}//?根據名稱獲取或創建對應xxxLoggerpublic ILogger CreateLogger(string name){//?根據名稱獲取,如果沒有,則根據傳入的委托方法進行創建????????return _loggers.GetOrAdd(name, loggerName => new ConsoleLogger(name, _messageQueue){Options = _options.CurrentValue,ScopeProvider = _scopeProvider});}......省略一些方法,私下研究.......} }
????
想了想,這里就不一一針對不同目的地(比如Trace、EventLog)扒代碼看了,不然說著說著就變成了代碼解讀了,如果有興趣,可以私下照著以下思路去看看代碼:
每一個目的地日志記錄都會有一個實現xxxLoggerProvider和對應的記錄器xxxLogger(真實記錄日志內容),LoggerFactory創建的Logger(暴露給程序員使用的)包含了對應的具體的記錄器,比如以寫入日志控制臺為例:
有一個ConsoleLoggerProvider的實現和對應的ConsoleLogger,ConsoleLoggerProvider負責通過名稱創建對應的ConsoleLogger,而LoggerFactory創建出來的Logger就是包含已注冊ConsoleLoggerProvider創建出來的ConsoleLogger;從而我們調用記錄日志方法的時候,其實最終是調用ConsoleLoggerProvider創建的ConsoleLogger對象方法;?
總結
本來想著日志應該用的很頻繁了,直接舉例演示就OK了,但是寫著寫著,用的多不一定清除關鍵步驟,于是又扒了下代碼,挑出了幾個關鍵方法簡單的說說,希望使用的小伙伴不困惑,深入研究就靠私下好好瞅瞅代碼了;
下一節實例演示日志的使用、日志的作用域、集成第三方日志框架進行日志擴展.....
------------------------------------------------
一個被程序搞丑的帥小伙,關注"Code綜藝圈",識別關注跟我一起學~~~
總結
以上是生活随笔為你收集整理的跟我一起学.NetCore之日志(Log)模型核心的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 大数据下的质量体系建设
- 下一篇: asp.net ajax控件工具集 Au