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