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

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 中的代码我的就不贴了,简单说一下其过滤规则的选择顺序:

  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. 「七天自制PHP框架」第四天:模型关联

    往期回顾:「七天自制PHP框架」第三天:PHP实现的设计模式,点击此处 原文地址:http://www.cnblogs.com/sweng/p/6624845.html,欢迎关注:编程老头 前阵子在网 ...

  2. 【Android Developers Training】 69. 视图切换的淡入淡出效果

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  3. java桥连接sql server之登录验证及对数据库增删改查

    一:步骤 1.sql server建立数据库和相关表 2.建立数据源  (1).打开控制面板找到管理,打开ODBC选项或直接搜索数据源  (2).打开数据源配置后点击添加,选择sql server点击 ...

  4. 【转】SQL多条件模糊查询解决方案-存储过程

    前言:   算法的基本特性在前几篇博客中已经做了详细的说明,经过不断的改进优化,到归仓的时候了,也就是说,该算法告一段落,不再更新. 作为最终的解决方案,简要的总结一下算法特性,以方便读者参阅. l ...

  5. web前段2017.6.8

    <body></body>background='图片路径'---表示背景图片图片:.jpg .png(透明图片) .gif(动态图)... 路径---绝对路径:相对于磁盘的路 ...

  6. js-txt文本处理

    js-txt文本处理 写自己主页项目时所产生的小问题拿出来给大家分享分享,以此共勉. ---DanlV TextArea的换行符处理 TextArea文本转换为Html:写入数据库时使用 js获取了t ...

  7. OpenGL的配置与搭建

    一.项目中所使用的OpenGL扩展库有: 1. freeglut-3.0.0 2. glew-2.0.0 3. glm-0.9.7.3 二.添加扩展库文件 在工程项目文件夹里面添加OpenGLExte ...

  8. 谈谈tableView的重要属性内边距

    全屏穿透效果需要做到两点 tableView的可视范围占据整个父控件(或者屏幕)--设置contentsize滚动范围. 所有的cell都可以被看到,也就是说tableView中的cell不会被导航栏 ...

  9. java中变量赋值的理解

    1.当赋值的值超出声明变量的范围时候,会报错! byte a =200 //会报错,因超出范围. byte a =(byte)200;//进行一个强制转换,就不会报错,不过会超出范围,超出部分会从头开 ...

  10. 输入三个整数x、y、z,请把这三个数由小到大输出

    题目:输入三个整数x,y,z,请把这三个数由小到大输出. 程序分析:我们想办法把最小的数放到x上,先将x与y进行比较,如果x> y则将x与y的值进行交换,然后再用x与z进行比较,如果x> ...