• 跟我一起学.NetCore之日志(Log)模型核心


    前言

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

    正文

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

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

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

    img

    运行结果:

    img

    咋样,使用还是依旧简单,这里是控制台程序,还需要写配置框架和依赖注入相关的代码逻辑,如果在WebAPI项目,直接就可以使用日志记录了,如下:

    img

    对于WebAPI项目而言,在项目启动流程分析的时候,就提到内部已经注册了相关服务了,所以才能这样如此简单的使用;

    难道日志就这样结束了吗?猜想看到这的小伙伴也不甘心,是的,得进一步了解,不需要特别深入,但至少得知道关键嘛,对不对?

    老规矩,程序中能看到日志相关点,当然就从这开始,看看是如何注册日志啊相关服务的:

    img

    对应代码:

    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();
                // 注册ILoggerFactory
                services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());
                // 注册ILogger
                services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>)));
                // 注册日志级别过滤,并默认设置级别为Information
                services.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和注册LoggerProvider
          public interface ILoggerFactory : IDisposable
          {
              // 根据名称创建ILogger    
              ILogger CreateLogger(string categoryName);
              // 注册ILoggerProvider
              void 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;
                  // 在原来基础上增加具体的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);
          }
      }
      // 创建具体的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
          {
              // 根据名称创建对应的Logger
              ILogger 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;
                  }
              }
              // 根据名称获取或创建对应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综艺圈",识别关注跟我一起学~~~

    img

  • 相关阅读:
    spring3创建RESTFul Web Service
    安装Maven
    如何使用 JSP JSTL 显示/制作树(tree) 菜单
    Eclipse EE导入maven工程
    安装PL/SQL Developer,链接本地64位Oracle
    Maven项目消除奇怪的红叉
    如何禁用Eclipse的Validating
    帮助文档总览
    MySQL下载安装、配置与使用(win7x64)
    C#知识点总结:Monitor和Lock以及区别
  • 原文地址:https://www.cnblogs.com/zoe-zyq/p/13569467.html
Copyright © 2020-2023  润新知