上一章,我們介紹了日志的配置,在熟悉了配置之后,自然是要了解一下在應用程序中如何使用,而本章則從最基本的使用開始,逐步去了解去源碼。
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。再往下看一下我們所調用的 LoggerFactory 的 CreateLogger
方法:
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 中的代碼我的就不貼了,簡單說一下其過濾規則的選擇順序:
- 首先查找指定了 ProviderName 並與當且 Provider的名稱(Alias)相同的過濾規則,如果沒有,則選用未指定 ProviderName 的過濾規則。
- 選擇指定的 CategoryName 相符合的過濾規則,如果沒有,則選擇未指定 CategoryName 的那一條。
- 如果符合的規則有多條,則選用最后一條。
- 如果未找到相符合的規則,則使用全局的最小過濾級別。
而且我們可以看到,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
factory.CreateLogger
。
總結
本章主要講解了 ILogger 的創建,過濾一系列過程,主要關注的是 Logger 的實現方式,而到此對 ASP.NET Core Logging 系統也有了一個基本的了解,在我們的應用程序中對 Logging 的配置和使用也算是游刃有余。而下一章則會介紹了一下 LoggerProvider 的實現以及如何自己來寫一個 LoggerProvider。