如何将.Net Core日志记录的核心机制改写为一个长尾词的?

2026-03-30 11:011阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计5003个文字,预计阅读时间需要21分钟。

如何将.Net Core日志记录的核心机制改写为一个长尾词的?

目录

一、前言

二、说明

三、开始

3.1 日志记录器工厂

3.1.1 ILoggerFactory 接口 3.1.2 LoggerFactory 实现 CreateLogger

3.2 日志记录器提供器

3.2.1 ILoggerProvider 接口

3.3 日志记录器

3.3.1 ILogger 接口 3.3.2 ILogger 特性

目录
  • 一、前言
  • 二、说明
  • 三、开始
    • 3.1 日志记录器工厂
      • 3.1.1 ILoggerFactory 接口
      • 3.1.2 LoggerFactory 实现
      • CreateLogger
    • 3.2日志记录提供器
      • 3.2.1 ILoggerProvider 接口
    • 3.3 日志记录器
      • 3.3.1 ILogger 接口
      • 3.3.2 Logger 实现
  • 四、总结

    一、前言

    回顾:日志记录之日志配置揭秘
    在上一篇中,我们已经了解了内置系统的默认配置和自定义配置的方式,在学习了配置的基础上,我们进一步的对日志在程序中是如何使用的深入了解学习。所以在这一篇中,主要是对日志记录的核心机制进行学习说明。

    二、说明

    在上一篇中,我们留下了两个问题

    日志记录的输出可以在哪里查看?而又由什么实现决定的呢?

    如何管理输出不同的日志呢?都有哪些方式呢?

    第一个问题:在官方的实现有:Console 、Debug 、EventSource 、EventLog 、TraceSource 、Azure App Service,还有一些第三方实现,当然了我们自己也是可以实现的。 是由ILoggerProvider接口来决定实现的。

    第二个问题:由 log Level、EventId、Logger Provider、Log filtering、Log category、Log scopes 合作解决。

    由上面的问题可以发现,我们可以实现多种不同的输出目标方式来实现写日志记录,但是又如何控制在写日志这个操作不变的情况下,实现不同的输入目标,这个时候我们就会想到,可以通过抽象的方式,将写日志这个操作动作抽象出来,而输出目标依赖这个动作实现具体的操作。所以当我们调用写日志操作方法的时候,由此依次调用对应的具体实现方法,把日志写到具体的目标上。

    这个过程具体是怎么实现的呢?我们接着往下看。

    三、开始

    其实在学习之前,我们应该都已经了解.net core框架有一个重要的特征就是依赖注入,通过在应用启动时候,将各种定义好的实现类型放入到一个集合容器中,通过在运行时,将从集合容器中取出放入对应的类型中。

    日志记录的的实现方式也离不开这个。下面让我们一起来看看。

    3.1 日志记录器工厂

    3.1.1 ILoggerFactory 接口

    public interface ILoggerFactory : IDisposable { ILogger CreateLogger(string categoryName); void AddProvider(ILoggerProvider provider); }

    ILoggerFactory是日志记录器的工厂接口类,用于配置日志记录系统并创建Logger实例的类,默认实现两个接口方法为,通过CreateLogger()方法来创建ILogger实例,(其中参数categoryName是一个日志类别,用于调用Logger所在类的全名,类别指明日志消息是谁写入的,一般我们将日志所属的的组件、服务或者消息类型名称作为日志类别。) 而AddProvider()添加日志记录提供程序,向日志系统注册添加一个ILoggerProvider。工厂接口类的默认实现类为LoggerFactory, 我们继续往下看:

    3.1.2 LoggerFactory 实现

    ILoggerFactory的默认实现是LoggerFactory,在构造函数中,如下:

    public class LoggerFactory : ILoggerFactory { private static readonly LoggerRuleSelector RuleSelector = new LoggerRuleSelector(); private readonly Dictionary<string, Logger> _loggers = new Dictionary<string, Logger>(StringComparer.Ordinal); private readonly List<ProviderRegistration> _providerRegistrations = new List<ProviderRegistration>(); private readonly object _sync = new object(); private volatile bool _disposed; private IDisposable _changeTokenRegistration; private LoggerFilterOptions _filterOptions; private LoggerExternalScopeProvider _scopeProvider; public LoggerFactory() : this(Enumerable.Empty<ILoggerProvider>()) { } public LoggerFactory(IEnumerable<ILoggerProvider> providers) : this(providers, new StaticFilterOptionsMonitor(new LoggerFilterOptions())) { } public LoggerFactory(IEnumerable<ILoggerProvider> providers, LoggerFilterOptions filterOptions) : this(providers, new StaticFilterOptionsMonitor(filterOptions)) { } public LoggerFactory(IEnumerable<ILoggerProvider> providers, IOptionsMonitor<LoggerFilterOptions> filterOption) { foreach (var provider in providers) { AddProviderRegistration(provider, dispose: false); } _changeTokenRegistration = filterOption.OnChange(RefreshFilters); RefreshFilters(filterOption.CurrentValue); } private void AddProviderRegistration(ILoggerProvider provider, bool dispose) { _providerRegistrations.Add(new ProviderRegistration { Provider = provider, ShouldDispose = dispose }); if (provider is ISupportExternalScope supportsExternalScope) { if (_scopeProvider == null) { _scopeProvider = new LoggerExternalScopeProvider(); } supportsExternalScope.SetScopeProvider(_scopeProvider); } } }

    LoggerFactory中 的构造函数中可以发现,通过注入的方式获取到ILoggerProvider(这个在下文中会说明),并调用AddProviderRegistration方法添加注册程序,将ILoggerProvider保存到ProviderRegistration集合中。

    AddProviderRegistration方法:

    这是一个日志程序提供器,将ILoggerProvider保存到ProviderRegistration集合中。当日志提供器实现ISupportExternalScope接口将单例LoggerExternalScopeProvider保存到 provider._scopeProvider 中。

    ProviderRegistration集合:

    private struct ProviderRegistration { public ILoggerProvider Provider; public bool ShouldDispose; }

    其中的ShouldDispose字段标识在在LoggerFactory生命周期结束之后,该ILoggerProvider是否需要释放。虽然在系统中LoggerFactory为单例模式,但是其提供了一个静态方法生成一个可释放的DisposingLoggerFactory

    LoggerFactory实现默认的接口方法CreateLogger(),AddProvider()

    查看源码如下:

    CreateLogger

    创建ILogger实例,CreateLogger()源码如下:

    public class LoggerFactory : ILoggerFactory { private readonly Dictionary<string, Logger> _loggers = new Dictionary<string, Logger>(StringComparer.Ordinal); private readonly List<ProviderRegistration> _providerRegistrations = new List<ProviderRegistration>(); private struct ProviderRegistration { public ILoggerProvider Provider; public bool ShouldDispose; } public ILogger CreateLogger(string categoryName) { if (CheckDisposed()) { throw new ObjectDisposedException(nameof(LoggerFactory)); } lock (_sync) { if (!_loggers.TryGetValue(categoryName, out var logger)) { logger = new Logger { Loggers = CreateLoggers(categoryName), }; (logger.MessageLoggers, logger.ScopeLoggers) = ApplyFilters(logger.Loggers); _loggers[categoryName] = logger; } return logger; } } private LoggerInformation[] CreateLoggers(string categoryName) { var loggers = new LoggerInformation[_providerRegistrations.Count]; for (var i = 0; i < _providerRegistrations.Count; i++) { loggers[i] = new LoggerInformation(_providerRegistrations[i].Provider, categoryName); } return loggers; } }

    从源码可以看出,CreateLogger方法中,会检测资源是否被释放,在方法中,根据内部定义的字典集合Dictionary<string, Logger> _loggers,判断字典中是否存在对应的Logger属性对象,如果不存在,会调用CreateLoggers方法根据之前注册的的所有ILoggerProvider所创建出来ProviderRegistration集合来实现创建Logger属性集合(根据日志类别生成了对应实际的日志写入类FileLoggerConsoleLogger等),并通过字典集合的方式保存categoryName和对应的Logger

    创建Logger需要的LoggerInformation[]

    internal readonly struct LoggerInformation { public LoggerInformation(ILoggerProvider provider, string category) : this() { ProviderType = provider.GetType(); Logger = provider.CreateLogger(category); Category = category; ExternalScope = provider is ISupportExternalScope; } public ILogger Logger { get; } public string Category { get; } public Type ProviderType { get; } public bool ExternalScope { get; } }

    根据注册的ILoggerProvider,创建ILogger其中的字段说明:

    Logger :具体日志类别写入途径实现类

    Category : 日志类别名称

    ProviderType : 日志提供器Type

    ExternalScope :是否支持 ExternalScope

    继续看CreateLogger方法,在创建Logger之后,还调用了ApplyFilters方法:

    private (MessageLogger[] MessageLoggers, ScopeLogger[] ScopeLoggers) ApplyFilters(LoggerInformation[] loggers) { var messageLoggers = new List<MessageLogger>(); var scopeLoggers = _filterOptions.CaptureScopes ? new List<ScopeLogger>() : null; foreach (var loggerInformation in loggers) { RuleSelector.Select(_filterOptions, loggerInformation.ProviderType, loggerInformation.Category, out var minLevel, out var filter); if (minLevel != null && minLevel > LogLevel.Critical) { continue; } messageLoggers.Add(new MessageLogger(loggerInformation.Logger, loggerInformation.Category, loggerInformation.ProviderType.FullName, minLevel, filter)); if (!loggerInformation.ExternalScope) { scopeLoggers?.Add(new ScopeLogger(logger: loggerInformation.Logger, externalScopeProvider: null)); } } if (_scopeProvider != null) { scopeLoggers?.Add(new ScopeLogger(logger: null, externalScopeProvider: _scopeProvider)); } return (messageLoggers.ToArray(), scopeLoggers?.ToArray()); }

    由源码可以看出,

    MessageLogger[]集合取值:

    在获取LoggerInformation[]后进行传参,进行遍历,根据RuleSelector过滤器,从配置文件中读取对应的日志级别,过滤器会返回获取最低级别和对应的一条过滤规则,如果配置文件中没有对应的配置,默认取全局最低级别(MinLevel),如果读取到的日志级别大于LogLevel.Critical,则将其加入MessageLogger[]

    过滤器的规则:

    选择当前记录器类型的规则,如果没有,请选择未指定记录器类型的规则

    选择最长匹配类别的规则

    如果没有与类别匹配的内容,则采用所有没有类别的规则

    如果只有一条规则,则使用它的级别和过滤器

    如果有多个规则,请选择使用最后一条。

    如果没有适用的规则,请使用全局最低级别

    通过MessageLogger[]添加消息日志集合

    internal readonly struct MessageLogger { public MessageLogger(ILogger logger, string category, string providerTypeFullName, LogLevel? minLevel, Func<string, string, LogLevel, bool> filter) { Logger = logger; Category = category; ProviderTypeFullName = providerTypeFullName; MinLevel = minLevel; Filter = filter; } public ILogger Logger { get; } public string Category { get; } private string ProviderTypeFullName { get; } public LogLevel? MinLevel { get; } public Func<string, string, LogLevel, bool> Filter { get; } public bool IsEnabled(LogLevel level) { if (MinLevel != null && level < MinLevel) { return false; } if (Filter != null) { return Filter(ProviderTypeFullName, Category, level); } return true; } } internal readonly struct ScopeLogger { public ScopeLogger(ILogger logger, IExternalScopeProvider externalScopeProvider) { Logger = logger; ExternalScopeProvider = externalScopeProvider; } public ILogger Logger { get; } public IExternalScopeProvider ExternalScopeProvider { get; } public IDisposable CreateScope<TState>(TState state) { if (ExternalScopeProvider != null) { return ExternalScopeProvider.Push(state); } return Logger.BeginScope<TState>(state); } }

    MessageLogger[]中带有MinLevel属性和Filter委托两种过滤配置,而这两种配置的来源,在上一章中可以看到,分别是从配置文件(AddConfiguration)和直接使用委托(AddFilter)来进行配置的。

    再由上面的IsEnabled方法可以看出,会先使用MinLevel过滤,再使用Filter进行过滤。所以这两者存在优先级。

    ScopeLogger[ ]取值 :

    如果ILoggerProvider实现了ISupportExternalScope接口,那么使用LoggerExternalScopeProvider作为Scope功能的实现。反之,使用ILogger作为其Scope功能的实现。

    LoggerExternalScopeProvider

    • 通过Scope组成了一个单向链表,每次beginscope向链表末端增加一个新的元素,Dispose的时候,删除链表最末端的元素。我们知道LoggerExternalScopeProvider在系统中是单例模式,多个请求进来,加入线程池处理。通过使用AsyncLoca来实现不同线程间数据独立。
    • 有两个地方开启了日志作用域:
    • 1、通过socket监听到请求后,将KestrelConnection加入线程池,线程池调度执行IThreadPoolWorkItem.Execute()方法。在这里开启了一次
    • 2、在构建请求上下文对象的时候(HostingApplication.CreateContext()),开启了一次

    由上源码可以得出:在工厂记录器类中,通过系统依赖注入的方式解析所有注册的ILoggerProvider,然后调用其中的CreateLogger方法实现创建一个Logger实例对象,而这个Logger实例对象会根据根据注册的ILoggerProvider创建需要的LoggerInformation[],并将此对象作为参数进行ApplyFilters过滤器筛选,得到对应的最低等级或过滤规则,最后通过调用Log方法日志记录的时候,会遍历MessageLogger[]集合,根据logger日志类别对应实际不同的日志写入类,调用ILoggerProvider具体实现类 (可以看下文说明) 中的Log方法。

    AddProviderRegistration→CreateLoggers→LoggerInformation[]→ApplyFilters→MessageLogger[]→Log→ILoggerProvider ( 执行具体类中的Log方法 )

    如何将.Net Core日志记录的核心机制改写为一个长尾词的?

    ILoggerFactory来源

    在上一篇中我们在对日志配置进行说明的时候,应用程序在启动初始化的时候会通过注入的方式CreateDefaultBuilderConfigureLoggingAddLogging

    public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure) { if (services == null) { throw new ArgumentNullException(nameof(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; }

    实现将把ILoggerFactory对象以依赖注入的方式托管到集合容器中,为程序调用提供使用。

    3.2日志记录提供器

    3.2.1 ILoggerProvider 接口

    创建ILogger实例的类型,根据日志类别名称创建一个新的ILogger实例

    public interface ILoggerProvider : IDisposable { ILogger CreateLogger(string categoryName); }

    这个是具体的日志写入类,在工厂记录器中我们已经提到了这个,在LoggerInformation[]中会根据日志类别注册对应的ILoggerProvider,在系统中我们就可以通过ILogger同时向多个途经写入日志信息。(这也是对上一篇中留下的问题进行再次说明)

    ILoogerProvider继承了IDisposable接口,如果某个具体的ILoggerProvider对象需要释放资源,就可以将相关的操作实现在Dispose方法中。

    默认的实现方式为多个,官方实现的由ConsoleLoggerProviderDebugLoggerProviderEventSourceLoggerProviderEventLogLoggerProviderTraceSourceLoggerProvider

    ConsoleLoggerProvider为列

    [ProviderAlias("Console")] public class ConsoleLoggerProvider : ILoggerProvider, ISupportExternalScope { private readonly IOptionsMonitor<ConsoleLoggerOptions> _options; 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(); if (RuntimeInformation.IsOSPlatform(OSPlatform.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; } } public ILogger CreateLogger(string name) { return _loggers.GetOrAdd(name, loggerName => new ConsoleLogger(name, _messageQueue) { Options = _options.CurrentValue, ScopeProvider = _scopeProvider }); } public void Dispose() { _optionsReloadToken?.Dispose(); _messageQueue.Dispose(); } public void SetScopeProvider(IExternalScopeProvider scopeProvider) { _scopeProvider = scopeProvider; foreach (var logger in _loggers) { logger.Value.ScopeProvider = _scopeProvider; } } }

    ConsoleLoggerProvider类型定义中,标注了ProviderAliasAttribute特性,并设置别名为Console,所以在配置过滤规则的时候,可以直接使用这个名称。ILogger的创建实现了具体日志类ConsoleLogger

    3.3 日志记录器

    3.3.1 ILogger 接口

    表示用于执行日志记录的类型,是系统中写入日志的统一入口。

    public interface ILogger { 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); }

    定义了三个方法,Log<TState>()用于写入日志,IsEnabled()用于检查判断日志级别是否开启,BeginScope()用于指日志作用域。

    3.3.2 Logger 实现

    ILogger执行记录接口类的具体实现Logger如下:

    internal class Logger : ILogger { public LoggerInformation[] Loggers { get; set; } public MessageLogger[] MessageLoggers { get; set; } public ScopeLogger[] ScopeLoggers { get; set; } 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; 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); } } } public bool IsEnabled(LogLevel logLevel) { var loggers = MessageLoggers; if (loggers == null) { return false; } List<Exception> exceptions = null; var i = 0; for (; i < loggers.Length; i++) { ref readonly var loggerInfo = ref loggers[i]; if (!loggerInfo.IsEnabled(logLevel)) { continue; } if (LoggerIsEnabled(logLevel, loggerInfo.Logger, ref exceptions)) { break; } } if (exceptions != null && exceptions.Count > 0) { ThrowLoggingError(exceptions); } return i < loggers.Length ? true : false; static bool LoggerIsEnabled(LogLevel logLevel, ILogger logger, ref List<Exception> exceptions) { try { if (logger.IsEnabled(logLevel)) { return true; } } catch (Exception ex) { if (exceptions == null) { exceptions = new List<Exception>(); } exceptions.Add(ex); } return false; } } }

    源码中MessageLogger[]在上文已经提到了,其中保存了在配置中启用的那些对应的ILogger

    需要注意的是,由于配置文件更改后,会调用ApplyFilters()方法,并为MessageLogger[]赋新值,所以在遍历之前,需要保存当前值,再进行处理。否则会出现修改异常。

    在系统中统一写入日志的入口,通过日志等级作为参数调用其IsEnabled方法来确定当前日志是否执行对应具体日志的实现类,当符合条件执行具体日志输出到对应的写入途径中会调用对应的Log方法(需要提供一个EventId来标识当前日志事件)

    ILogger默认的实现方式为多个,官方实现的由ConsoleLoggerDebugLoggerEventSourceLoggerEventLogLoggerTraceSourceLogger具体日志实现类代表不同的日志写入途径。

    四、总结

    • ILoggerFactoryILoggerProvider中都会通过方法创建ILogger对象,但两者是不相同的。在工厂默认实现LoggerFactory类型中它创建的ILogger对象是由注册到LoggerFactory对象上的所有ILoggerProvider对象提供一组ILogger对象组合而成。而日志提供器ILoggerProvider创建的ILogger是日志实现输出到对应的渠道目标,写入日志。
    • 日志记录器ILogger中的Log()方法会记录执行日志,在日志记录器工厂ILoggerFactory和日志记录提供器ILoggerProvider中两种不同的ILogger实现对应的Log()方法实现的意思也是不同的。在ILoggerFactory产生的是ILogger类型(也就是我们最终使用的Logger),其Log()方法是依次调用Logger中包含的LoggerInformation[]数组中的ILogger。而ILoggerProvider产生的为各类不同的XxxLogger(也就是上面说的Logger中的LoggerInformation数组包含的如ConsoleLogger、DebugLogger),其Log()方法是把日志写到具体的目标上去。
    • 由上文可以发现,在asp.net core提供的日志记录的组件,通过工厂的一种方式,将日志记录器和日志记录提供器都放入到工厂这样的容器中,满足定义多个不同的记录方式。在后续我们可以通过自定义ILoggerProvider集成到Logger中,实现自己需要的日志记录输出方式。
    • 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。
    • 官方源码和参考资料

    到此这篇关于.Net Core日志记录核心机制的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持易盾网络。

    本文共计5003个文字,预计阅读时间需要21分钟。

    如何将.Net Core日志记录的核心机制改写为一个长尾词的?

    目录

    一、前言

    二、说明

    三、开始

    3.1 日志记录器工厂

    3.1.1 ILoggerFactory 接口 3.1.2 LoggerFactory 实现 CreateLogger

    3.2 日志记录器提供器

    3.2.1 ILoggerProvider 接口

    3.3 日志记录器

    3.3.1 ILogger 接口 3.3.2 ILogger 特性

    目录
    • 一、前言
    • 二、说明
    • 三、开始
      • 3.1 日志记录器工厂
        • 3.1.1 ILoggerFactory 接口
        • 3.1.2 LoggerFactory 实现
        • CreateLogger
      • 3.2日志记录提供器
        • 3.2.1 ILoggerProvider 接口
      • 3.3 日志记录器
        • 3.3.1 ILogger 接口
        • 3.3.2 Logger 实现
    • 四、总结

      一、前言

      回顾:日志记录之日志配置揭秘
      在上一篇中,我们已经了解了内置系统的默认配置和自定义配置的方式,在学习了配置的基础上,我们进一步的对日志在程序中是如何使用的深入了解学习。所以在这一篇中,主要是对日志记录的核心机制进行学习说明。

      二、说明

      在上一篇中,我们留下了两个问题

      日志记录的输出可以在哪里查看?而又由什么实现决定的呢?

      如何管理输出不同的日志呢?都有哪些方式呢?

      第一个问题:在官方的实现有:Console 、Debug 、EventSource 、EventLog 、TraceSource 、Azure App Service,还有一些第三方实现,当然了我们自己也是可以实现的。 是由ILoggerProvider接口来决定实现的。

      第二个问题:由 log Level、EventId、Logger Provider、Log filtering、Log category、Log scopes 合作解决。

      由上面的问题可以发现,我们可以实现多种不同的输出目标方式来实现写日志记录,但是又如何控制在写日志这个操作不变的情况下,实现不同的输入目标,这个时候我们就会想到,可以通过抽象的方式,将写日志这个操作动作抽象出来,而输出目标依赖这个动作实现具体的操作。所以当我们调用写日志操作方法的时候,由此依次调用对应的具体实现方法,把日志写到具体的目标上。

      这个过程具体是怎么实现的呢?我们接着往下看。

      三、开始

      其实在学习之前,我们应该都已经了解.net core框架有一个重要的特征就是依赖注入,通过在应用启动时候,将各种定义好的实现类型放入到一个集合容器中,通过在运行时,将从集合容器中取出放入对应的类型中。

      日志记录的的实现方式也离不开这个。下面让我们一起来看看。

      3.1 日志记录器工厂

      3.1.1 ILoggerFactory 接口

      public interface ILoggerFactory : IDisposable { ILogger CreateLogger(string categoryName); void AddProvider(ILoggerProvider provider); }

      ILoggerFactory是日志记录器的工厂接口类,用于配置日志记录系统并创建Logger实例的类,默认实现两个接口方法为,通过CreateLogger()方法来创建ILogger实例,(其中参数categoryName是一个日志类别,用于调用Logger所在类的全名,类别指明日志消息是谁写入的,一般我们将日志所属的的组件、服务或者消息类型名称作为日志类别。) 而AddProvider()添加日志记录提供程序,向日志系统注册添加一个ILoggerProvider。工厂接口类的默认实现类为LoggerFactory, 我们继续往下看:

      3.1.2 LoggerFactory 实现

      ILoggerFactory的默认实现是LoggerFactory,在构造函数中,如下:

      public class LoggerFactory : ILoggerFactory { private static readonly LoggerRuleSelector RuleSelector = new LoggerRuleSelector(); private readonly Dictionary<string, Logger> _loggers = new Dictionary<string, Logger>(StringComparer.Ordinal); private readonly List<ProviderRegistration> _providerRegistrations = new List<ProviderRegistration>(); private readonly object _sync = new object(); private volatile bool _disposed; private IDisposable _changeTokenRegistration; private LoggerFilterOptions _filterOptions; private LoggerExternalScopeProvider _scopeProvider; public LoggerFactory() : this(Enumerable.Empty<ILoggerProvider>()) { } public LoggerFactory(IEnumerable<ILoggerProvider> providers) : this(providers, new StaticFilterOptionsMonitor(new LoggerFilterOptions())) { } public LoggerFactory(IEnumerable<ILoggerProvider> providers, LoggerFilterOptions filterOptions) : this(providers, new StaticFilterOptionsMonitor(filterOptions)) { } public LoggerFactory(IEnumerable<ILoggerProvider> providers, IOptionsMonitor<LoggerFilterOptions> filterOption) { foreach (var provider in providers) { AddProviderRegistration(provider, dispose: false); } _changeTokenRegistration = filterOption.OnChange(RefreshFilters); RefreshFilters(filterOption.CurrentValue); } private void AddProviderRegistration(ILoggerProvider provider, bool dispose) { _providerRegistrations.Add(new ProviderRegistration { Provider = provider, ShouldDispose = dispose }); if (provider is ISupportExternalScope supportsExternalScope) { if (_scopeProvider == null) { _scopeProvider = new LoggerExternalScopeProvider(); } supportsExternalScope.SetScopeProvider(_scopeProvider); } } }

      LoggerFactory中 的构造函数中可以发现,通过注入的方式获取到ILoggerProvider(这个在下文中会说明),并调用AddProviderRegistration方法添加注册程序,将ILoggerProvider保存到ProviderRegistration集合中。

      AddProviderRegistration方法:

      这是一个日志程序提供器,将ILoggerProvider保存到ProviderRegistration集合中。当日志提供器实现ISupportExternalScope接口将单例LoggerExternalScopeProvider保存到 provider._scopeProvider 中。

      ProviderRegistration集合:

      private struct ProviderRegistration { public ILoggerProvider Provider; public bool ShouldDispose; }

      其中的ShouldDispose字段标识在在LoggerFactory生命周期结束之后,该ILoggerProvider是否需要释放。虽然在系统中LoggerFactory为单例模式,但是其提供了一个静态方法生成一个可释放的DisposingLoggerFactory

      LoggerFactory实现默认的接口方法CreateLogger(),AddProvider()

      查看源码如下:

      CreateLogger

      创建ILogger实例,CreateLogger()源码如下:

      public class LoggerFactory : ILoggerFactory { private readonly Dictionary<string, Logger> _loggers = new Dictionary<string, Logger>(StringComparer.Ordinal); private readonly List<ProviderRegistration> _providerRegistrations = new List<ProviderRegistration>(); private struct ProviderRegistration { public ILoggerProvider Provider; public bool ShouldDispose; } public ILogger CreateLogger(string categoryName) { if (CheckDisposed()) { throw new ObjectDisposedException(nameof(LoggerFactory)); } lock (_sync) { if (!_loggers.TryGetValue(categoryName, out var logger)) { logger = new Logger { Loggers = CreateLoggers(categoryName), }; (logger.MessageLoggers, logger.ScopeLoggers) = ApplyFilters(logger.Loggers); _loggers[categoryName] = logger; } return logger; } } private LoggerInformation[] CreateLoggers(string categoryName) { var loggers = new LoggerInformation[_providerRegistrations.Count]; for (var i = 0; i < _providerRegistrations.Count; i++) { loggers[i] = new LoggerInformation(_providerRegistrations[i].Provider, categoryName); } return loggers; } }

      从源码可以看出,CreateLogger方法中,会检测资源是否被释放,在方法中,根据内部定义的字典集合Dictionary<string, Logger> _loggers,判断字典中是否存在对应的Logger属性对象,如果不存在,会调用CreateLoggers方法根据之前注册的的所有ILoggerProvider所创建出来ProviderRegistration集合来实现创建Logger属性集合(根据日志类别生成了对应实际的日志写入类FileLoggerConsoleLogger等),并通过字典集合的方式保存categoryName和对应的Logger

      创建Logger需要的LoggerInformation[]

      internal readonly struct LoggerInformation { public LoggerInformation(ILoggerProvider provider, string category) : this() { ProviderType = provider.GetType(); Logger = provider.CreateLogger(category); Category = category; ExternalScope = provider is ISupportExternalScope; } public ILogger Logger { get; } public string Category { get; } public Type ProviderType { get; } public bool ExternalScope { get; } }

      根据注册的ILoggerProvider,创建ILogger其中的字段说明:

      Logger :具体日志类别写入途径实现类

      Category : 日志类别名称

      ProviderType : 日志提供器Type

      ExternalScope :是否支持 ExternalScope

      继续看CreateLogger方法,在创建Logger之后,还调用了ApplyFilters方法:

      private (MessageLogger[] MessageLoggers, ScopeLogger[] ScopeLoggers) ApplyFilters(LoggerInformation[] loggers) { var messageLoggers = new List<MessageLogger>(); var scopeLoggers = _filterOptions.CaptureScopes ? new List<ScopeLogger>() : null; foreach (var loggerInformation in loggers) { RuleSelector.Select(_filterOptions, loggerInformation.ProviderType, loggerInformation.Category, out var minLevel, out var filter); if (minLevel != null && minLevel > LogLevel.Critical) { continue; } messageLoggers.Add(new MessageLogger(loggerInformation.Logger, loggerInformation.Category, loggerInformation.ProviderType.FullName, minLevel, filter)); if (!loggerInformation.ExternalScope) { scopeLoggers?.Add(new ScopeLogger(logger: loggerInformation.Logger, externalScopeProvider: null)); } } if (_scopeProvider != null) { scopeLoggers?.Add(new ScopeLogger(logger: null, externalScopeProvider: _scopeProvider)); } return (messageLoggers.ToArray(), scopeLoggers?.ToArray()); }

      由源码可以看出,

      MessageLogger[]集合取值:

      在获取LoggerInformation[]后进行传参,进行遍历,根据RuleSelector过滤器,从配置文件中读取对应的日志级别,过滤器会返回获取最低级别和对应的一条过滤规则,如果配置文件中没有对应的配置,默认取全局最低级别(MinLevel),如果读取到的日志级别大于LogLevel.Critical,则将其加入MessageLogger[]

      过滤器的规则:

      选择当前记录器类型的规则,如果没有,请选择未指定记录器类型的规则

      选择最长匹配类别的规则

      如果没有与类别匹配的内容,则采用所有没有类别的规则

      如果只有一条规则,则使用它的级别和过滤器

      如果有多个规则,请选择使用最后一条。

      如果没有适用的规则,请使用全局最低级别

      通过MessageLogger[]添加消息日志集合

      internal readonly struct MessageLogger { public MessageLogger(ILogger logger, string category, string providerTypeFullName, LogLevel? minLevel, Func<string, string, LogLevel, bool> filter) { Logger = logger; Category = category; ProviderTypeFullName = providerTypeFullName; MinLevel = minLevel; Filter = filter; } public ILogger Logger { get; } public string Category { get; } private string ProviderTypeFullName { get; } public LogLevel? MinLevel { get; } public Func<string, string, LogLevel, bool> Filter { get; } public bool IsEnabled(LogLevel level) { if (MinLevel != null && level < MinLevel) { return false; } if (Filter != null) { return Filter(ProviderTypeFullName, Category, level); } return true; } } internal readonly struct ScopeLogger { public ScopeLogger(ILogger logger, IExternalScopeProvider externalScopeProvider) { Logger = logger; ExternalScopeProvider = externalScopeProvider; } public ILogger Logger { get; } public IExternalScopeProvider ExternalScopeProvider { get; } public IDisposable CreateScope<TState>(TState state) { if (ExternalScopeProvider != null) { return ExternalScopeProvider.Push(state); } return Logger.BeginScope<TState>(state); } }

      MessageLogger[]中带有MinLevel属性和Filter委托两种过滤配置,而这两种配置的来源,在上一章中可以看到,分别是从配置文件(AddConfiguration)和直接使用委托(AddFilter)来进行配置的。

      再由上面的IsEnabled方法可以看出,会先使用MinLevel过滤,再使用Filter进行过滤。所以这两者存在优先级。

      ScopeLogger[ ]取值 :

      如果ILoggerProvider实现了ISupportExternalScope接口,那么使用LoggerExternalScopeProvider作为Scope功能的实现。反之,使用ILogger作为其Scope功能的实现。

      LoggerExternalScopeProvider

      • 通过Scope组成了一个单向链表,每次beginscope向链表末端增加一个新的元素,Dispose的时候,删除链表最末端的元素。我们知道LoggerExternalScopeProvider在系统中是单例模式,多个请求进来,加入线程池处理。通过使用AsyncLoca来实现不同线程间数据独立。
      • 有两个地方开启了日志作用域:
      • 1、通过socket监听到请求后,将KestrelConnection加入线程池,线程池调度执行IThreadPoolWorkItem.Execute()方法。在这里开启了一次
      • 2、在构建请求上下文对象的时候(HostingApplication.CreateContext()),开启了一次

      由上源码可以得出:在工厂记录器类中,通过系统依赖注入的方式解析所有注册的ILoggerProvider,然后调用其中的CreateLogger方法实现创建一个Logger实例对象,而这个Logger实例对象会根据根据注册的ILoggerProvider创建需要的LoggerInformation[],并将此对象作为参数进行ApplyFilters过滤器筛选,得到对应的最低等级或过滤规则,最后通过调用Log方法日志记录的时候,会遍历MessageLogger[]集合,根据logger日志类别对应实际不同的日志写入类,调用ILoggerProvider具体实现类 (可以看下文说明) 中的Log方法。

      AddProviderRegistration→CreateLoggers→LoggerInformation[]→ApplyFilters→MessageLogger[]→Log→ILoggerProvider ( 执行具体类中的Log方法 )

      如何将.Net Core日志记录的核心机制改写为一个长尾词的?

      ILoggerFactory来源

      在上一篇中我们在对日志配置进行说明的时候,应用程序在启动初始化的时候会通过注入的方式CreateDefaultBuilderConfigureLoggingAddLogging

      public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure) { if (services == null) { throw new ArgumentNullException(nameof(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; }

      实现将把ILoggerFactory对象以依赖注入的方式托管到集合容器中,为程序调用提供使用。

      3.2日志记录提供器

      3.2.1 ILoggerProvider 接口

      创建ILogger实例的类型,根据日志类别名称创建一个新的ILogger实例

      public interface ILoggerProvider : IDisposable { ILogger CreateLogger(string categoryName); }

      这个是具体的日志写入类,在工厂记录器中我们已经提到了这个,在LoggerInformation[]中会根据日志类别注册对应的ILoggerProvider,在系统中我们就可以通过ILogger同时向多个途经写入日志信息。(这也是对上一篇中留下的问题进行再次说明)

      ILoogerProvider继承了IDisposable接口,如果某个具体的ILoggerProvider对象需要释放资源,就可以将相关的操作实现在Dispose方法中。

      默认的实现方式为多个,官方实现的由ConsoleLoggerProviderDebugLoggerProviderEventSourceLoggerProviderEventLogLoggerProviderTraceSourceLoggerProvider

      ConsoleLoggerProvider为列

      [ProviderAlias("Console")] public class ConsoleLoggerProvider : ILoggerProvider, ISupportExternalScope { private readonly IOptionsMonitor<ConsoleLoggerOptions> _options; 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(); if (RuntimeInformation.IsOSPlatform(OSPlatform.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; } } public ILogger CreateLogger(string name) { return _loggers.GetOrAdd(name, loggerName => new ConsoleLogger(name, _messageQueue) { Options = _options.CurrentValue, ScopeProvider = _scopeProvider }); } public void Dispose() { _optionsReloadToken?.Dispose(); _messageQueue.Dispose(); } public void SetScopeProvider(IExternalScopeProvider scopeProvider) { _scopeProvider = scopeProvider; foreach (var logger in _loggers) { logger.Value.ScopeProvider = _scopeProvider; } } }

      ConsoleLoggerProvider类型定义中,标注了ProviderAliasAttribute特性,并设置别名为Console,所以在配置过滤规则的时候,可以直接使用这个名称。ILogger的创建实现了具体日志类ConsoleLogger

      3.3 日志记录器

      3.3.1 ILogger 接口

      表示用于执行日志记录的类型,是系统中写入日志的统一入口。

      public interface ILogger { 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); }

      定义了三个方法,Log<TState>()用于写入日志,IsEnabled()用于检查判断日志级别是否开启,BeginScope()用于指日志作用域。

      3.3.2 Logger 实现

      ILogger执行记录接口类的具体实现Logger如下:

      internal class Logger : ILogger { public LoggerInformation[] Loggers { get; set; } public MessageLogger[] MessageLoggers { get; set; } public ScopeLogger[] ScopeLoggers { get; set; } 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; 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); } } } public bool IsEnabled(LogLevel logLevel) { var loggers = MessageLoggers; if (loggers == null) { return false; } List<Exception> exceptions = null; var i = 0; for (; i < loggers.Length; i++) { ref readonly var loggerInfo = ref loggers[i]; if (!loggerInfo.IsEnabled(logLevel)) { continue; } if (LoggerIsEnabled(logLevel, loggerInfo.Logger, ref exceptions)) { break; } } if (exceptions != null && exceptions.Count > 0) { ThrowLoggingError(exceptions); } return i < loggers.Length ? true : false; static bool LoggerIsEnabled(LogLevel logLevel, ILogger logger, ref List<Exception> exceptions) { try { if (logger.IsEnabled(logLevel)) { return true; } } catch (Exception ex) { if (exceptions == null) { exceptions = new List<Exception>(); } exceptions.Add(ex); } return false; } } }

      源码中MessageLogger[]在上文已经提到了,其中保存了在配置中启用的那些对应的ILogger

      需要注意的是,由于配置文件更改后,会调用ApplyFilters()方法,并为MessageLogger[]赋新值,所以在遍历之前,需要保存当前值,再进行处理。否则会出现修改异常。

      在系统中统一写入日志的入口,通过日志等级作为参数调用其IsEnabled方法来确定当前日志是否执行对应具体日志的实现类,当符合条件执行具体日志输出到对应的写入途径中会调用对应的Log方法(需要提供一个EventId来标识当前日志事件)

      ILogger默认的实现方式为多个,官方实现的由ConsoleLoggerDebugLoggerEventSourceLoggerEventLogLoggerTraceSourceLogger具体日志实现类代表不同的日志写入途径。

      四、总结

      • ILoggerFactoryILoggerProvider中都会通过方法创建ILogger对象,但两者是不相同的。在工厂默认实现LoggerFactory类型中它创建的ILogger对象是由注册到LoggerFactory对象上的所有ILoggerProvider对象提供一组ILogger对象组合而成。而日志提供器ILoggerProvider创建的ILogger是日志实现输出到对应的渠道目标,写入日志。
      • 日志记录器ILogger中的Log()方法会记录执行日志,在日志记录器工厂ILoggerFactory和日志记录提供器ILoggerProvider中两种不同的ILogger实现对应的Log()方法实现的意思也是不同的。在ILoggerFactory产生的是ILogger类型(也就是我们最终使用的Logger),其Log()方法是依次调用Logger中包含的LoggerInformation[]数组中的ILogger。而ILoggerProvider产生的为各类不同的XxxLogger(也就是上面说的Logger中的LoggerInformation数组包含的如ConsoleLogger、DebugLogger),其Log()方法是把日志写到具体的目标上去。
      • 由上文可以发现,在asp.net core提供的日志记录的组件,通过工厂的一种方式,将日志记录器和日志记录提供器都放入到工厂这样的容器中,满足定义多个不同的记录方式。在后续我们可以通过自定义ILoggerProvider集成到Logger中,实现自己需要的日志记录输出方式。
      • 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。
      • 官方源码和参考资料

      到此这篇关于.Net Core日志记录核心机制的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持易盾网络。