• .Net Core中的日志组件(Logging)以及源码解析


    1、介绍

      Logging组件是微软实现的日志记录组件包括控制台(Console)、调试(Debug)、事件日志(EventLog)和TraceSource,但是没有实现最常用用的文件记录日志功能(可以用其他第三方的如log4net,NLog和Serilog)。

    2.1 默认配置

            private readonly IBlogService _blogService;
            private readonly ILogger<BlogController> _logger;
            /// <summary>
            /// 构造函数
            /// </summary>
            /// <param name="blogService"></param>
            /// <param name="logger"></param>
            public BlogController(IBlogService blogService, ILogger<BlogController> logger)
            {
                _blogService = blogService;
                _logger = logger;
            }
            /// <summary>
            /// 
            /// </summary>
            /// <returns></returns>
            [HttpGet]
            public MethodResult TestLogger()
            {
                _logger.LogInformation("正在请求Test接口!");
                _logger.LogWarning("三方请求接口,请求终止!");
                _logger.LogError("操作异常!");
                _blogService.Test();
                return new MethodResult(1);
            }

    运行结果:

    写到这里是不是有个疑问,为啥我就仅仅写了注入就起作用了呢?下面我们来看一下源码实现。

    我们点进 WebHost.CreateDefaultBuilder(args) 可以看到源码微软有对 ConfigureLogging 的实现。 

    2.2  IServiceCollection注入AddLogging

    services.AddLogging(builder =>
                {
                    builder.AddConfiguration(Configuration.GetSection("Logging"))
                    .AddConsole()
                    .AddDebug();
                });

    2.3  注入ILoggerFactory 创建ILogger

    //注入ILoggerFactory 创建ILogger
                ILogger logger = loggerFactory.CreateLogger<Program>();
                logger.LogInformation("非主机模式输出log message");

    2.4 Logging源码解析

    三种配置其实都是为了注入日志相关的服务,但是调用的方法稍有不同。现在我们以第二种配置来详细看看其注入过程。首先调用AddLogging方法,其实现源码如下:
    public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure)
            {
                if (services == null)
                {
                    throw new ArgumentNullException("services");
                }
    
                services.AddOptions();
                services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());
                services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>)));
                services.TryAddEnumerable(ServiceDescriptor.Singleton((IConfigureOptions<LoggerFilterOptions>)new DefaultLoggerLevelConfigureOptions(LogLevel.Information)));
                configure(new LoggingBuilder(services));
                return services;
            }

    接着会调用AddConfiguration

    public static ILoggingBuilder AddConfiguration(this ILoggingBuilder builder, IConfiguration configuration)
            {
                builder.AddConfiguration();
           //下面为AddConfiguration的实现
            public static void AddConfiguration(this ILoggingBuilder builder)
            {
              builder.Services.TryAddSingleton<ILoggerProviderConfigurationFactory, LoggerProviderConfigurationFactory>();
              builder.Services.TryAddSingleton(typeof(ILoggerProviderConfiguration<>), typeof(LoggerProviderConfiguration<>));
            }
    
                builder.Services.AddSingleton<IConfigureOptions<LoggerFilterOptions>>(new LoggerFilterConfigureOptions(configuration));
                builder.Services.AddSingleton<IOptionsChangeTokenSource<LoggerFilterOptions>>(new ConfigurationChangeTokenSource<LoggerFilterOptions>(configuration));
                builder.Services.AddSingleton(new LoggingConfiguration(configuration));
    
                return builder;
            }

    下面来看打印日志的具体实现:

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)        
    {
           var loggers = Loggers;
                List<Exception> exceptions = null;
           //loggers为LoggerInformation数组,如果你在Startup中添加了Console、Deubg日志功能了,那loggers数组值有2个,就是它俩。
                foreach (var loggerInfo in loggers)
                {  //循环遍历每一种日志打印,如果满足些日子的条件,才执行打印log方法。比如某一个日志等级为Info,
              //但是Console配置的最低打印等级为Warning,Debug配置的最低打印等级为Debug
              //则Console中不会打印,Debug中会被打印
                    if (!loggerInfo.IsEnabled(logLevel))
                    {
                        continue;
                    }
                    try
                    {
                //每一种类型的日志,对应的打印方法不同。执行对应的打印方法
                        loggerInfo.Logger.Log(logLevel, eventId, state, exception, formatter);
                    }
                    catch (Exception ex)
                    {
                        if (exceptions == null)
                        {
                            exceptions = new List<Exception>();
                        }
    
                        exceptions.Add(ex);
                    }
                }
        }

    下面具体看一下Console的打印实现:

    首先ConsoleLogger实现了ILogger的Log方法,并在方法中调用WriteMessage方法

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
            {
           //代码太多 我就省略一些判空代码
                var message = formatter(state, exception);
    
                if (!string.IsNullOrEmpty(message) || exception != null)
                {
                    WriteMessage(logLevel, Name, eventId.Id, message, exception);
                }
            }
    
            public virtual void WriteMessage(LogLevel logLevel, string logName, int eventId, string message, Exception exception)
            {
           .......
                if (logBuilder.Length > 0)
                {
                    var hasLevel = !string.IsNullOrEmpty(logLevelString);
                    //这里是主要的代码实现,可以看到,并没有写日志的代码,而是将日志打入到一个BlockingCollection<LogMessageEntry>队列中
             //这里需要指出 BlockingCollection是线程安全的集合,多个线程同时访问,不会发生数据混乱。
                    _queueProcessor.EnqueueMessage(new LogMessageEntry()
                    {
                        Message = logBuilder.ToString(),
                        MessageColor = DefaultConsoleColor,
                        LevelString = hasLevel ? logLevelString : null,
                        LevelBackground = hasLevel ? logLevelColors.Background : null,
                        LevelForeground = hasLevel ? logLevelColors.Foreground : null
                    });
                }
           ......
            }

    下面看日志被放入队列后的具体实现:
    public class ConsoleLoggerProcessor : IDisposable
    {
        private const int _maxQueuedMessages = 1024;
    
        private readonly BlockingCollection<LogMessageEntry> _messageQueue = new BlockingCollection<LogMessageEntry>(_maxQueuedMessages);
        private readonly Thread _outputThread;
    
       public IConsole Console;
    
            public ConsoleLoggerProcessor()
            {
                //在构造函数中启动一个线程,执行ProcessLogQueue方法
           //从下面ProcessLogQueue方法可以看出,是循环遍历集合,将集合中的数据打印
    
                _outputThread = new Thread(ProcessLogQueue)
                {
                    IsBackground = true,
                    Name = "Console logger queue processing thread"public virtual void EnqueueMessage(LogMessageEntry message)
            {
                if (!_messageQueue.IsAddingCompleted)
                {
                    try
                    {
                        _messageQueue.Add(message);
                        return;
                    }
                    catch (InvalidOperationException) { }
                }
    
                WriteMessage(message);
            }
    
            internal virtual void WriteMessage(LogMessageEntry message)
            {
                if (message.LevelString != null)
                {
                    Console.Write(message.LevelString, message.LevelBackground, message.LevelForeground);
                }
    
                Console.Write(message.Message, message.MessageColor, message.MessageColor);
                Console.Flush();
            }
    
            private void ProcessLogQueue()
            {
        
                try
            {
              //GetConsumingEnumerable()方法比较特殊,当集合中没有值时,会阻塞自己,一但有值了,直到集合中又有元素继续遍历
              foreach (var message in _messageQueue.GetConsumingEnumerable())
                    {
                        WriteMessage(message);
                    }
                }
                catch
                {
                    try
                    {
                        _messageQueue.CompleteAdding();
                    }
                    catch { }
                }
            }
      }
  • 相关阅读:
    Mayan游戏 (codevs 1136)题解
    虫食算 (codevs 1064)题解
    靶形数独 (codevs 1174)题解
    黑白棋游戏 (codevs 2743)题解
    神经网络 (codevs 1088) 题解
    The Rotation Game (POJ 2286) 题解
    倒水问题 (codevs 1226) 题解
    银河英雄传说 (codevs 1540) 题解
    生日蛋糕 (codevs 1710) 题解
    第一章 1.11 高阶函数
  • 原文地址:https://www.cnblogs.com/ZhengHengWU/p/13590756.html
Copyright © 2020-2023  润新知