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。

转载于:https://www.cnblogs.com/RainingNight/p/asp-net-core-logging-logger.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/400296.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【RK3399Pro学习笔记】七、ROS订阅者Subscriber的编程实现

目录如何实现一个订阅者C编写程序配置CMakeLists.txt编译并运行发布者python创建并编写脚本运行平台&#xff1a;华硕 Thinker Edge R 瑞芯微 RK3399Pro 固件版本&#xff1a;Tinker_Edge_R-Debian-Stretch-V1.0.4-20200615 记录自【古月居】古月ROS入门21讲 | 一学就会的ROS机…

C#操作sql通用类 SQLHelper

Codeusing System; using System.Data; using System.Configuration; using System.Web; using System.Web.Security; using System.Collections; using System.Data.SqlClient;/// <summary> /// 数据库的通用访问代码 /// 此类为抽象类&#xff0c;不允许实例化&#x…

PHP中变量类型的判断

PHP中变量类型的判断 一、gettype() gettype 会根据 参数类型返回下列值 “boolean”&#xff08;从 PHP 4 起&#xff09; “integer” “double”&#xff08;如果是 float 则返回“double”&#xff0c;而不是“float”&#xff09; “string” “array” “object”…

SqlServer优化:当数据量查询不是特别多,但数据库服务器的CPU资源一直100%时,如何优化?...

最近和同事处理一个小程序&#xff0c;数据量不是特别大&#xff0c;某表的的数据记录&#xff1a;7000W条记录左右&#xff0c;但是从改别执行一次查询时&#xff0c;却发现查询速度也不快&#xff0c;而且最明显的问题就是CPU100%。 sql语句&#xff1a; select gridid,lng,l…

时间比金钱金贵得多

工资是一个非常直观的参数&#xff0c;所有人都会被它的数字所诱惑&#xff0c;但工资的背后&#xff0c;体现的是你劳动的性价比&#xff0c;是万不可用工资去交换。 第一个故事 一个35岁左右的白骨精来找我们&#xff0c;她需要在两个工作里面做选择。一个是年薪30万的制片总…

【RK3399Pro学习笔记】八、ROS话题消息的定义与使用

目录自定义话题消息定义msg文件在package.xml中添加功能包依赖在CMakeLists.txt添加编译选项编译结果使用C编写程序person_publisher.cppperson_subscriber.cpp配置CMakeLists.txt编译并运行python编写程序person_publisher.py运行平台&#xff1a;华硕 Thinker Edge R 瑞芯微 …

ios 打电话结束返回到应用中

在我们做打电话这个功能时&#xff0c;我们常常是调用这个方法&#xff1a; [[UIApplication sharedApplication] openURL:[NSURL URLWithString:"tel://xxxxx"]]; 然而&#xff0c;这样实现了功能后&#xff0c;结束通话后&#xff0c;确不能回到自己的应用中来。最…

php 字符符转整数

intval() $my_month intval($my_format_date[1]);

C语言及程序设计进阶例程-17 认识链表

贺老师教学链接 C语言及程序设计进阶 本课讲解 例 建立并输出一个简单链表 #include <stdio.h> struct Student {int num;float score;struct Student *next; }; int main( ) {struct Student a,b,c,*head,*p;a. num31001;a.score89.5;b. num31003;b.score90;c. num3100…

【RK3399Pro学习笔记】九、ROS客户端Client的编程实现

目录如何实现一个客户端C创建功能包编写程序配置CMakeLists.txt编译并运行python创建并编写脚本运行平台&#xff1a;华硕 Thinker Edge R 瑞芯微 RK3399Pro 固件版本&#xff1a;Tinker_Edge_R-Debian-Stretch-V1.0.4-20200615 记录自【古月居】古月ROS入门21讲 | 一学就会的…

(原创)UML要点总结

今天我们总结要点&#xff1a; 我们就从这张图慢慢讲。 一。类图部分 基础&#xff1a; 类图→长方形表示。类名在最上栏&#xff0c;下面是数据&#xff0c;第三栏是方法。其存在两种关系&#xff1a;关联和泛化 属性&#xff1a; 全形&#xff1a; 可见性 名&#xff1a;类…

Android - Okhttp拦截器

Okhttp-wiki 之 Interceptors 拦截器转载于:https://www.cnblogs.com/qlky/p/7298470.html

PHP 函数 - 返回值

<?php function sum($x,$y) {$z$x$y;return $z; }echo "5 10 " . sum(5,10) . "<br>"; echo "7 13 " . sum(7,13) . "<br>"; echo "2 4 " . sum(2,4); ?> http://www.w3school.com.cn/php/php_f…

Docker解析及轻量级PaaS平台演练(一)--Docker简介与安装

版权声明&#xff1a;本文为博主原创文章&#xff0c;未经博主允许不得转载。 https://blog.csdn.net/qq1010885678/article/details/46290985 Container技术&#xff1a; 传统的虚拟化技术&#xff1a; 通过对硬件层模拟&#xff0c;从而实现了能够在一套硬件上面运行多个操作…

杭电4639

居然在最后的输出形式那里卡了很久&#xff0c;忘记除10007&#xff0c;忘记输出Case 希望下次不要犯这种错误 #include "iostream" #include "string.h" using namespace std; int f[10100][100],maxb0; char list[110][10100]; int max(int a,int b){ret…

【RK3399Pro学习笔记】十、ROS服务端Server的编程实现

目录如何实现一个服务器C编写程序配置CMakeLists.txt编译并运行python创建并编写脚本运行平台&#xff1a;华硕 Thinker Edge R 瑞芯微 RK3399Pro 固件版本&#xff1a;Tinker_Edge_R-Debian-Stretch-V1.0.4-20200615 记录自【古月居】古月ROS入门21讲 | 一学就会的ROS机器人入…

通过WordPress内置函数批量添加文章

http://www.php.cn/blog/detail/2482.html 最近业务需要在网站上批量添加大量的文章。一篇一篇地手动添加绝对会搞死我&#xff0c;所以&#xff0c;我就开始寻找批量添加的方法。其实&#xff0c;文章的相关内容都已经在本地的数据库里了。我最先想到的方法是通过sql语句直接…

关于Unity3D中函数说明

Camera.SetReplacementShader(Shader shader , String replacementTag); 说明&#xff1a; 根据replacementTag设置以后的相机渲染用哪个shader Camera.RenderWithShader(Shader shader , String replacementTag); 说明: 根据replacementTag设置立即使用shader渲染&#xff0…

团队作业-第二周-测试计划

第1章引言 1.1目的 本测试计划文档作为指导此测试项目循序渐进的基础&#xff0c;帮助我们安排合适的资源和进度&#xff0c;避免可能的风险。本文档有助于实现以下目标&#xff1a; 1) 确定现有项目的信息和应测试的软件结构。 2) 列出推荐的测试需求 3) 推荐可采用的测试策略…

【RK3399Pro学习笔记】十一、ROS服务数据的定义与使用

目录自定义服务数据定义srv文件在package.xml中添加功能包依赖在CMakeLists.txt添加编译选项编译生成语言相关文件使用C编写程序person_server.cppperson_client.cpp配置CMakeLists.txt编译并运行python编写程序person_server.pyperson_client.py运行平台&#xff1a;华硕 Thin…