Fork me on GitHub
RainingNight
雨夜朦胧

ASP.NET Core 源码学习之 Logging[3]:Logger

上一章,我们介绍了日志的配置,在熟悉了配置之后,自然是要了解一下在应用程序中如何使用,而本章则从最基本的使用开始,逐步去了解去源码。

LoggerFactory

我们可以在构造函数中注入 ILoggerFactory,来创建一个日志记录器:

public class TestController : Controller
{
    private readonly ILogger _logger;

    public TestController(ILoggerFactory factory)
    {
        _logger = factory.CreateLogger(nameof(TestController));
    }

    public void TestLog()
    {
        _logger.LogInformation("info");
        _logger.LogDebug("debug");
    }
}

在上一章中我们有介绍到,ILoggerFactory 的默认实现是 LoggerFactory。而 LoggerFactory 中的代码较多,我们慢慢来看:

首先是构造函数:

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)
{
    _providerRegistrations = providers.Select(provider => new ProviderRegistration { Provider = provider }).ToList();
    _changeTokenRegistration = filterOption.OnChange(RefreshFilters);
    RefreshFilters(filterOption.CurrentValue);
}

看到这里不得不先说一下 ILoggerProvider

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

而我们知道,LoggerFactory 也有一个 CreateLogger 方法,那他们之间有什么区别呢,后面会解释。

而现在我们可以猜到,在具体的 Provider 中,如 AddConsole 扩展方法,只是简单的将注册了一个 ILoggerProvider 的实例,这样,在DI系统创建 LoggerFactory 实例时,便能够解析到所有注册的 Provider。再往下看一下我们所调用的 LoggerFactoryCreateLogger 方法:

public ILogger CreateLogger(string categoryName)
{
    if (CheckDisposed())
    {
        throw new ObjectDisposedException(nameof(LoggerFactory));
    }
    lock (_sync)
    {
        Logger logger;
        if (!_loggers.TryGetValue(categoryName, out logger))
        {

            logger = new Logger()
            {
                Loggers = CreateLoggers(categoryName)
            };
            _loggers[categoryName] = logger;
        }
        return logger;
    }
}

private LoggerInformation[] CreateLoggers(string categoryName)
{
    var loggers = new LoggerInformation[_providerRegistrations.Count];
    for (int i = 0; i < _providerRegistrations.Count; i++)
    {
        var provider = _providerRegistrations[i].Provider;

        loggers[i].Logger = provider.CreateLogger(categoryName);
        loggers[i].ProviderType = provider.GetType();
    }

    ApplyRules(loggers, categoryName, 0, loggers.Length);
    return loggers;
}

首先是直接 new 了一个 Logger 实例,然后为其 Loggers 属性赋值,而 Loggers 属性则是由注册的所有 ILoggerProvider 所创建出来的 Logger 集合。

回到刚才的问题,有两种 CreateLogger 方法,也就有两种 Logger,一种是直接 New 出来的 Logger,是 ILogger 的默认实现者,也是我们记录日志时所直接调用的 Logger,另外一种则是由 ILoggerProvider 所创建出来的 Logger,姑且称之为 PLogger 吧。而在我们的应用程序中记录日志时,只需要关注 Logger 就可以了,而不需要去关注 PLogger,因为 Logger 是一个日志记录器的聚合,包含所有注册的 PLogger,PLogger 则是具体的执行者,我们可以通过代码来更清楚的了解:

internal class Logger : ILogger
{
    public LoggerInformation[] Loggers { get; set; }

    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        var loggers = Loggers;
        if (loggers == null)
        {
            return;
        }

        List<Exception> exceptions = null;
        foreach (var loggerInfo in loggers)
        {
            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);
            }
        }

        if (exceptions != null && exceptions.Count > 0)
        {
            throw new AggregateException(
                message: "An error occurred while writing to logger(s).", innerExceptions: exceptions);
        }
    }

    public bool IsEnabled(LogLevel logLevel)
    {
       ...
    }

    public IDisposable BeginScope<TState>(TState state)
    {
      ...
    }
}

可以看到,Logger 的 Log 方法只是依次调用 Loggers 属性中的 Log 方法,而其本身并不具有记录日志的功能。

再继续把视线转回到 LoggerFactory 类,在其 CreateLoggers 方法中,最后还调用了 ApplyRules,这便是我们上一章中所配置的过滤器的用武之地了。

private void ApplyRules(LoggerInformation[] loggers, string categoryName, int start, int count)
{
    for (var index = start; index < start + count; index++)
    {
        ref var loggerInformation = ref loggers[index];

        RuleSelector.Select(_filterOptions,
            loggerInformation.ProviderType,
            categoryName,
            out var minLevel,
            out var filter);

        loggerInformation.Category = categoryName;
        loggerInformation.MinLevel = minLevel;
        loggerInformation.Filter = filter;
    }
}

RuleSelector 中的代码我的就不贴了,简单说一下其过滤规则的选择顺序:

  1. 首先查找指定了 ProviderName 并与当且 Provider的名称(Alias)相同的过滤规则,如果没有,则选用未指定 ProviderName 的过滤规则。
  2. 选择指定的 CategoryName 相符合的过滤规则,如果没有,则选择未指定 CategoryName 的那一条。
  3. 如果符合的规则有多条,则选用最后一条。
  4. 如果未找到相符合的规则,则使用全局的最小过滤级别。

而且我们可以看到,LoggerInformation 带有 MinLeve 属性和 Filter 委托两种过滤配置,而这两种配置的来源,在上一章中可以看到,分别是从配置文件(AddConfiguration)和直接使用委托(AddFilter)来进行配置的。而他们的优先级又是怎么样的?

internal struct LoggerInformation
{
    public ILogger Logger { get; set; }

    public string Category { get; set; }

    public Type ProviderType { get; set; }

    public LogLevel? MinLevel { get; set; }

    public Func<string, string, LogLevel, bool> Filter { get; set; }

    public bool IsEnabled(LogLevel level)
    {
        if (MinLevel != null && level < MinLevel)
        {
            return false;
        }

        if (Filter != null)
        {
            return Filter(ProviderType.FullName, Category, level);
        }

        return true;
    }
}

无需多说,通过上面的 IsEnabled 方法能够清楚的看到,先使用 MinLevel 过滤,再使用 Filter 进行过滤。

再总结一下其整个流程:首先是注册 Provider,然后 LogFactory 通过DI系统解析所有注册的 Privoder,在我们调用其 CreateLogger 方法时,创建一个 Logger 对象,并通过这些 Provider 创建一个 LoggerInformation 集合赋予给 Logger 对象,并包含 PLogger 和 经过筛选后的过滤规则,最后在我们调用 Log 方法记录日志时,会遍历 LoggerInformation 集合,然后执行过滤方法,再调用 PLogger 中的 Log 方法。

ILogger

上面介绍了使用 LogFactory 创建 ILogger 的方式,其实还有一种更简单的使用方式,我们可以直接在构造函数中注入 ILogger<T> 来记录日志:

public class TestController : Controller
{
    private readonly ILogger _logger;

    public TestController(ILogger<AccountController> logger)
    {
        _logger = logger;
    }
}

是不是很神奇,那么是如何实现的呢?

public interface ILogger<out TCategoryName> : ILogger
{
    
}

而 ILogger 的默认实现,在上一章中介绍的 AddLogging 方法中,我们知道是 Logger<T>

public class Logger<T> : ILogger<T>
{
    private readonly ILogger _logger;

    /// <summary>
    /// Creates a new <see cref="Logger{T}"/>.
    /// </summary>
    /// <param name="factory">The factory.</param>
    public Logger(ILoggerFactory factory)
    {
        if (factory == null)
        {
            throw new ArgumentNullException(nameof(factory));
        }

        _logger = factory.CreateLogger(TypeNameHelper.GetTypeDisplayName(typeof(T)));
    }

    IDisposable ILogger.BeginScope<TState>(TState state)
    {
        return _logger.BeginScope(state);
    }

    bool ILogger.IsEnabled(LogLevel logLevel)
    {
        return _logger.IsEnabled(logLevel);
    }

    void ILogger.Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter)
    {
        _logger.Log(logLevel, eventId, state, exception, formatter);
    }
}

可以看到,在 Logger 其实是一种简写形式,使用泛型的方式来获取 categoryName,最后还是调用了 factory.CreateLogger

总结

本章主要讲解了 ILogger 的创建,过滤一系列过程,主要关注的是 Logger 的实现方式,而到此对 ASP.NET Core Logging 系统也有了一个基本的了解,在我们的应用程序中对 Logging 的配置和使用也算是游刃有余。而下一章则会介绍了一下 LoggerProvider 的实现以及如何自己来写一个 LoggerProvider。

posted @ 2017-08-06 14:35  雨夜朦胧  阅读(3332)  评论(1编辑  收藏  举报