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

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。

ASP.NET Core 源码学习之 Logging[3]:Logger的更多相关文章

  1. ASP.NET Core 源码学习之 Logging[1]:Introduction

    在ASP.NET 4.X中,我们通常使用 log4net, NLog 等来记录日志,但是当我们引用的一些第三方类库使用不同的日志框架时,就比较混乱了.而在 ASP.Net Core 中内置了日志系统, ...

  2. ASP.NET Core 源码学习之 Logging[2]:Configure

    在上一章中,我们对 ASP.NET Logging 系统做了一个整体的介绍,而在本章中则开始从最基本的配置开始,逐步深入到源码当中去. 默认配置 在 ASP.NET Core 2.0 中,对默认配置做 ...

  3. 【ASP.NET Core 】ASP.NET Core 源码学习之 Logging[1]:Introduction

    在ASP.NET 4.X中,我们通常使用 log4net, NLog 等来记录日志,但是当我们引用的一些第三方类库使用不同的日志框架时,就比较混乱了.而在 ASP.Net Core 中内置了日志系统, ...

  4. ASP.NET Core 源码学习之 Logging[4]:FileProvider

    前面几章介绍了 ASP.NET Core Logging 系统的配置和使用,而对于 Provider ,微软也提供了 Console, Debug, EventSource, TraceSource ...

  5. ASP.NET Core源码学习(一)Hosting

    ASP.NET Core源码的学习,我们从Hosting开始, Hosting的GitHub地址为:https://github.com/aspnet/Hosting.git 朋友们可以从以上链接克隆 ...

  6. ASP.NET Core 源码学习之 Options[1]:Configure

    配置的本质就是字符串的键值对,但是对于面向对象语言来说,能使用强类型的配置是何等的爽哉! 目录 ASP.NET Core 配置系统 强类型的 Options Configure 方法 源码解析 ASP ...

  7. ASP.NET Core 源码学习之 Options[4]:IOptionsMonitor

    前面我们讲到 IOptions 和 IOptionsSnapshot,他们两个最大的区别便是前者注册的是单例模式,后者注册的是 Scope 模式.而 IOptionsMonitor 则要求配置源必须是 ...

  8. asp.net core源码飘香:Logging组件

    简介: 作为基础组件,日志组件被其他组件和中间件所使用,它提供了一个统一的编程模型,即不需要知道日志最终记录到哪里去,只需要调用它即可. 使用方法很简单,通过依赖注入ILogFactory(Creat ...

  9. ASP.NET Core 源码学习之 Options[2]:IOptions

    在上一篇中,介绍了一下Options的注册,而使用时只需要注入IOption即可: public ValuesController(IOptions<MyOptions> options) ...

随机推荐

  1. 数据处理包dplyr的函数

    dplyr专注处理dataframe对象, 并提供更稳健的与其它数据库对象间的接口. 一.5个关键的数据处理函数: select()   返回列的子集filter()     返回行的子集arrang ...

  2. html中p标签行间距的问题

    使用CSS行高样式line-height可以设置调整p行间距,但是同时会影响每行文字间的上下间距,所以使用line-height虽然可以用来设置html p 行距离间隔,但是不是很实用,一般line- ...

  3. Kafka官方文档翻译——设计

    下面是博主的公众号,后续会发布和讨论一系列分布式消息队列相关的内容,欢迎关注. ------------------------------------------------------------ ...

  4. uwp版的音乐播放器练手

    UWP项目之音乐播放器 这个项目本来是我女朋友的一个小作业,她做不出来,结果只能是我来代劳.经过几天的时间虽然赶出来了,但是自己不是很满意,还有很多不满意的地方,因此决定在最近的一段时间内,重新完成. ...

  5. hibernate in List查询条件 sum求和使用参考

    @Override public Integer getSumZongShuByidList(List<String> idList){ Integer zongshu = 0; Stri ...

  6. linux 下载文件到本地磁盘的命令是什么

    linux下可以直接运行命令下载或上传文件1.检查并安装相应的包:yum install lrzsz2.使用 sz 文件名 现在相应的文件到本地磁盘.3.上传使用rz 选择相应文件即可.

  7. 原生ajax封装,数据初始化,

    var ajaxTool = { setting : { method : 'get', url : location.href, data : '', callback : function(){a ...

  8. 最近快速的过了一遍php基础语法

    把in_array() 写成is_array() ;结果自己坑了自己一万:打脸一万下,先记账上

  9. CSS学习(页外引用还不懂)

    CSS的语法结构为   选择符 {属性:值:}    Selector {Property : Value:} 选择符:通配 *{....}  , 元素  body{....} .h1{....}.p ...

  10. Delphi临界区的使用

    在开发一个平板点餐软件后台订单打印程序时,使用线程订单打印,为防打印阻塞使用临界区. 类: type MYPARA=record title:pchar; //标题 str:pchar; flag:i ...