跟我一起学.NetCore之日志(Log)模型核心
发布日期:2021-05-09 09:32:00 浏览次数:2 分类:博客文章

本文共 10526 字,大约阅读时间需要 35 分钟。

前言

鲁迅都说:没有日志的系统不能上线(鲁迅说:这句我没说过,但是在理)!日志对于一个系统而言,特别重要,不管是用于事务审计,还是用于系统排错,还是用于安全追踪.....都扮演了很重要的角色;之前有很多第三方的日志框架也很给力,如Log4Net、NLog和Serilog等,在.NetCore中也集成了日志模型,使用便捷,同时很方便的与第三方日志框架进行集成扩展;

正文

实例演示之前,先了解一下日志级别,后续如果不想输出全部日志,可以通过日志级别进行过滤,同时通过日志级别可以标注日志内容的重要程度:

namespace Microsoft.Extensions.Logging{    // 日志级别从下往上递增,所以根据级别可以过滤掉低级别的日志信息    public enum LogLevel    {        Trace,        Debug,        Information,        Warning,        Error,        Critical,        None    }}

来一个控制台程序实例演示:

运行结果:

咋样,使用还是依旧简单,这里是控制台程序,还需要写配置框架和依赖注入相关的代码逻辑,如果在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
configure) { if (services == null) { throw new ArgumentNullException("services"); } // 为了支持Options选项,得注册Options相关服务,上篇讲过 services.AddOptions(); // 注册ILoggerFactory services.TryAdd(ServiceDescriptor.Singleton
()); // 注册ILogger services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>))); // 注册日志级别过滤,并默认设置级别为Information services.TryAddEnumerable(ServiceDescriptor.Singleton((IConfigureOptions
)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
    (LogLevel logLevel, EventId eventId, TState state, Exception exception, Func
    formatter); // 判断对应的日志级别是否可用 bool IsEnabled(LogLevel logLevel); // 日志作用域 IDisposable BeginScope
    (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
    (LogLevel logLevel, EventId eventId, TState state, Exception exception, Func
    formatter) { var loggers = MessageLoggers; if (loggers == null) { return; }​ List
    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
    formatter, ref List
    exceptions, in TState state) { try { // 记录日志内容 logger.Log(logLevel, eventId, state, exception, formatter); } catch (Exception ex) { if (exceptions == null) { exceptions = new List
    (); }​ exceptions.Add(ex); } } }}

    ILoggerFactory/LoggerFactory

    namespace Microsoft.Extensions.Logging{    // 创建 ILogger和注册LoggerProvider    public interface ILoggerFactory : IDisposable    {        // 根据名称创建ILogger            ILogger CreateLogger(string categoryName);        // 注册ILoggerProvider        void AddProvider(ILoggerProvider provider);    }}........省略方法-私下研究......
    // LoggerFactory挑了几个关键方法进行说明// 创建Loggerpublic 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;    }}// 这个用于注册具体的xxxLoggerProviderpublic 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;            // 在原来基础上增加具体的xxxLogger            var 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);    }}// 创建具体的xxxLoggerprivate 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    {        // 根据名称创建对应的Logger        ILogger CreateLogger(string categoryName);    }}
    namespace Microsoft.Extensions.Logging.Console{    [ProviderAlias("Console")]    public class ConsoleLoggerProvider : ILoggerProvider, ISupportExternalScope    {        // 支持Options动态监听          private readonly IOptionsMonitor
    _options; // 缓存对应的xxxLogger private readonly ConcurrentDictionary
    _loggers; // 日志处理 private readonly ConsoleLoggerProcessor _messageQueue;​ private IDisposable _optionsReloadToken; private IExternalScopeProvider _scopeProvider = NullExternalScopeProvider.Instance; // 构造函数,初始化 public ConsoleLoggerProvider(IOptionsMonitor
    options) { _options = options; _loggers = new ConcurrentDictionary
    ();​ 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; } } // 根据名称获取或创建对应xxxLogger public 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综艺圈",识别关注跟我一起学~~~

转载地址:https://www.cnblogs.com/zoe-zyq/p/13569467.html 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:跟我一起学.NetCore之日志作用域及第三方日志框架扩展
下一篇:第一份工作没有工资,信不信

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2023年09月17日 09时35分08秒