.Net Core中的日志Logging使用以及源码解析
.Net Core中的日志Logging使用以及源码解析
在.Net Core中系统自带的集成了日志系统,看一下如何使用:
第一步先添加LogProvider,这个是为了告诉容器我们日志输出的来源,LogProvider的目的是创建Logger,在Asp.Net Core中默认添加了3个Provider:
1 |
|
这里添加的LoggerProvider最终都会被注入到LoggerFactory中,用于创建ILogger。
上述代码里还看到了AddConfiguration,这是通过配置添加日志过滤条件。后面会具体讲解。
添加完Provider之后,就可以从容器中获取日志对象,获取的时候有两种类型可以获取:
ILogger
:直接获取日志输出对象。 ILoggerFactory:获取日志工厂,然后调用
ILogger CreateLogger(string categoryName);
这个方法产生一个日志输出对象。实际上第一种方式,直接获取的ILogger<T,>内部也包含了一个ILogger对象,它最终也是调用的内部的ILogger进行操作,自己本身没有干任何事情。可以看一下ILogger
的实现类代码: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33public 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);
}
}从这里看出,如果以泛型的方式去获取,categoryName就是这个类的全称,基本上就等于typeof(T).FullName,这个会在后续的日志过滤中起到效果。
在Controller中使用日志
这是一个最简单的例子,实际记录日志的地方,一般不会在构造里面:
1 |
|
讲到这里你可能会有一些疑问:我添加了多个LogProvider,自然会产生多个ILogger,那这里通过工厂类产生的ILogger到底是哪一个?答案是:一个都不是!这里通过工厂产生的ILogger是系统的默认实现Microsoft.Extensions.Logging.Logger。而在这个默认实现Logger里,有一个LoggerInformation数组。LoggerInformation包含了这些LogProvider产生的ILogger和一些过滤条件,在不添加任何过滤条件的情况下,上述的代码同时会记录到3个不同的地方。
1 |
|
另外,如果想以注入的方式去获取ILogger是行不通的,因为在初始化的时候,不会向容器中注册ILogger的实现,只会注册ILoggerFactory和ILogger
日志过滤
在讲日志过滤的时候,我们要先明确日志过滤的筛选的条件有哪些:
Category:这个值就是在创建ILogger(系统默认的实现Logger)时,传递的那个categoryName参数。对于以ILogger
方式获取的日志对象,这个值就是T的类型全称。 ProviderType:这个值就很简单,就是创建该ILogger的LogProvider的类型。
LogLevel:日志记录的级别,这是一个系统定义的枚举类型:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42public enum LogLevel
{
/// <summary>
/// Logs that contain the most detailed messages. These messages may contain sensitive application data.
/// These messages are disabled by default and should never be enabled in a production environment.
/// </summary>
Trace = 0,
/// <summary>
/// Logs that are used for interactive investigation during development. These logs should primarily contain
/// information useful for debugging and have no long-term value.
/// </summary>
Debug = 1,
/// <summary>
/// Logs that track the general flow of the application. These logs should have long-term value.
/// </summary>
Information = 2,
/// <summary>
/// Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the
/// application execution to stop.
/// </summary>
Warning = 3,
/// <summary>
/// Logs that highlight when the current flow of execution is stopped due to a failure. These should indicate a
/// failure in the current activity, not an application-wide failure.
/// </summary>
Error = 4,
/// <summary>
/// Logs that describe an unrecoverable application or system crash, or a catastrophic failure that requires
/// immediate attention.
/// </summary>
Critical = 5,
/// <summary>
/// Not used for writing log messages. Specifies that a logging category should not write any messages.
/// </summary>
None = 6,
}
这几个参数和ILogger(LogProvider产生的)会一起被保存到LoggerInformation这个对象中。在LoggerInformation还有一个属性也是用做日志过滤的:
1 |
|
这是一个委托需要三个参数,分别代表Category,ProviderType,LogLevel,返回布尔值,代表是否启用该日志类型。当有了这些信息后,每当使用ILogger(系统的默认实现Logger)进行日志书写时,会循环遍历LoggerInformation数组对象,这样就可以独立控制每一个LogProvider产生的ILogger对象,根据不同的Provider,Category和LogLevel来决定是否需要输出日志。
接下来的问题就是,如何配置过滤规则?配置过滤规则唯一途径就是配置LoggerFilterOptions这个对象。如何自己配置一个Option对象,请参考我同系列的前一篇文章。
系统提供了一系列的的扩展方法帮我们去配置这个对象,详情可以参考:https://github.com/aspnet/Extensions/blob/master/src/Logging/Logging/src/FilterLoggingBuilderExtensions.cs,也有基于配置的扩展,其核心代码是:https://github.com/aspnet/Extensions/blob/master/src/Logging/Logging.Configuration/src/LoggerFilterConfigureOptions.cs 那这个LoggerFilterOptions又是什么呢,一起看一下源代码:
1 |
|
系统提供的所有扩展方法,都是往这个对象的Rules添加Rule,然后在创建一个ILogger(LogProvider创建的)的时候根据一定的规则去匹配到属于这个ILogger(LogProvider创建的)的那条Rule,赋值给LoggerInformation的Filter属性。也就是说,即使有很多条Rule,但是最终只会找到最符合条件的那条Rule。如果一条都找不到,那就使用MinLevel。默认情况下MinLevel是Information。而这个LoggerFilterRule包含的属性,就是前面提到的那几个筛选条件:
1 |
|
那查找Rule的匹配规则又是什么呢?核心代码如下(可以直接看后面的解释):
1 |
|
在配置规则时ProviderName默认应该是类的全称,而上面提到的ProviderAlias指的是该类的别称,在类上面添加ProviderAliasAttribute为它定义一个别称,方便配置。
再来看IsBetter方法:
- 如果传进来的Rule应用于某一个具体的Provider,并且和当前Log的Provider不一致,则直接返回False,因为不匹配
- 如果传进来的的Rule应用于某一个具体的Category(默认是类的全名),并且和当前Log的Category不匹配,则返回False,因为不匹配
- 如果当前的Rule应用于某一个具体的Provider,并且传进来的Rule不是应用于某一个具体的Provider,则返回False,因为当前的优先级更高
- 如果当前的Rule不是应用于某一个具体的Provider,并且传进来的Rule是应用于某一个具体的Provider(由于第一点的存在,传进来的Rule的Provider一定是和当前Log一致的,由于第二点的存在,如果传进来的Rule包含Category,则一定和当前的Log一致),则返回True,因为传进来的优先级更高
- 如果当前的Rule是应用于某一个具体的Category,并且传递进来的Rule不是应用于某一个具体的Category,则返回False,因为当前优先级更高
- 如果当前的Rule是应用于某一个具体的Category,并且当前传递进来的Rule也是应用于某一个具体的Category,但是当前的匹配度更高(字符串长度越长),则返回False,因为当前的优先级更高
- 其余都返回True
那么最终Rule的匹配度,由低到高应该是:
- ProviderName: null,CategoryName:null
- ProviderName: null,CategoryName:Matched(匹配的长度越长,说明越匹配)
- ProviderName: Matched,CategoryName:null
- ProviderName: Matched,CategoryName:Matched(匹配的长度越长,说明越匹配)
CategoryName的匹配度指的是字符串长度。如果一个Rule都没找到,那就使用MinLevel。
最佳实践
使用配置文件,去配置日志过滤条件,注意:使用配置文件时,只能设置特定的Provider和Category的LogLevel,不能指定委托类型的Filter。想要使用Filter,只能用编码的方式配置。来看一个日志配置实例:
1 |
|
在Logging下面的直接子节点LogLevel下面配置的,代表Proivder为null,同时子节点的名称代表Category,值代表LogLevel。
而Logging下面的其它直接子节点,节点名称代表ProviderName。其下的LogLevel子节点下面的子节点,名称代表Category,值代表LogLevel。
另外Category的值为Default,最终解析的时候会解析成null,不区分大小写。
这里的Console可以使用Microsoft.Extensions.Logging.Console.ConsoleLoggerProvider代替,因为ConsoleLoggerProvider有一个特性别称Console,所以可以不写全称。[ProviderAlias(“Console”)]
最终这个配置会被解释成:
- 非Console的日志记录时,只记录日志级别高于Debug
- Console的日志,如果Category不以CoreLogging.Controllers开头,则只记录级别高于Warning的
- Console的日志,如果Category以CoreLogging.Controllers开头但不以CoreLogging.Controllers.HomeController开头,则只记录级别高于Error的
- Console的日志,如果Category以CoreLogging.Controllers.HomeController开头,则只记录级别高于Info的
文中提到的源码都在https://github.com/zhurongbo111/AspNetCoreDemo/tree/master/03-Logging