跟我一起学.NetCore之日志(Log)模型核心

前言

鲁迅都说:没有日志的系统不能上线(鲁迅说:这句我没说过,但是在理)!日志对于一个系统而言,特别重要,不管是用于事务审计,还是用于系统排错,还是用于安全追踪.....都扮演了很重要的角色;之前有很多第三方的日志框架也很给力,如Log4Net、NLog和Serilog等,在.NetCore中也集成了日志模型,使用便捷,同时很方便的与第三方日志框架进行集成扩展;

正文

实例演示之前,先了解一下日志级别,后续如果不想输出全部日志,可以通过日志级别进行过滤,同时通过日志级别可以标注日志内容的重要程度:

namespace Microsoft.Extensions.Logging
{// 日志级别从下往上递增,所以根据级别可以过滤掉低级别的日志信息public enum LogLevel{Trace,Debug,Information,Warning,Error,Critical,None}
}

来一个控制台程序实例演示:

运行结果:

咋样,使用还是依旧简单,这里是控制台程序,还需要写配置框架和依赖注入相关的代码逻辑,如果在WebAPI项目,直接就可以使用日志记录了,如下:

对于WebAPI项目而言,在项目启动流程分析的时候,就提到内部已经注册了相关服务了,所以才能这样如此简单的使用;

难道日志就这样结束了吗?猜想看到这的小伙伴也不甘心,是的,得进一步了解,不需要特别深入,但至少得知道关键嘛,对不对?

老规矩,程序中能看到日志相关点,当然就从这开始,看看是如何注册日志啊相关服务的:

对应代码:

namespace Microsoft.Extensions.DependencyInjection
{// IServiceCollection的扩展方法,用于注册日志相关服务public static class LoggingServiceCollectionExtensions{public static IServiceCollection AddLogging(this IServiceCollection services){return services.AddLogging(delegate{});}// 核心方法,上面的方法就是调用下面这个public static IServiceCollection AddLogging(this IServiceCollection services, Action<ILoggingBuilder> configure){if (services == null){throw new ArgumentNullException("services");}// 为了支持Options选项,得注册Options相关服务,上篇讲过services.AddOptions();// 注册ILoggerFactoryservices.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());// 注册ILoggerservices.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>)));// 注册日志级别过滤,并默认设置级别为Informationservices.TryAddEnumerable(ServiceDescriptor.Singleton((IConfigureOptions<LoggerFilterOptions>)new DefaultLoggerLevelConfigureOptions(LogLevel.Information)));// 执行传入的委托方法configure(new LoggingBuilder(services));return services;}}
}

日志相关服务注册了解了,那接着看看关键实现,其实日志记录有三个核心类型:ILogger、ILoggerFactory和ILoggerProvider,对应的实现分别是Logger、LoggerFactory、xxxLoggerProvider;

  • xxxLoggerProvider:针对于不同的目的地创建对应的xxxLogger,这里的xxxLogger负责在目的地(文件、数据库、控制台等)写入内容;

  • LoggerFactory:负责创建Logger,其中包含所有注册的xxxLoggerProvider对应Logger;

  • Logger:以上两种;

    

扒开这三个类型的定义,简单看看都定义了什么....

  • ILogger/Logger

    namespace Microsoft.Extensions.Logging
    {public interface ILogger{// 记录日志方法,其中包含日志级别、事件ID、写入的内容、格式化内容等void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);// 判断对应的日志级别是否可用bool IsEnabled(LogLevel logLevel);// 日志作用域IDisposable BeginScope<TState>(TState state);}
    }
    

    Logger中挑了比较关键的属性和方法简单说说

    internal class Logger : ILogger
    {// 用于缓存真正Logger记录器的public LoggerInformation[] Loggers { get; set; }public MessageLogger[] MessageLoggers { get; set; }// 这个用于缓存日志作用域Loggers    public ScopeLogger[] ScopeLoggers { get; set; }// Log日志记录方法    public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter){var loggers = MessageLoggers;if (loggers == null){return;}List<Exception> exceptions = null;// 遍历对应的Loggers        for (var i = 0; i < loggers.Length; i++){ref readonly var loggerInfo = ref loggers[i];if (!loggerInfo.IsEnabled(logLevel)){continue;}// 执行内部方法LoggerLog(logLevel, eventId, loggerInfo.Logger, exception, formatter, ref exceptions, state);}if (exceptions != null && exceptions.Count > 0){ThrowLoggingError(exceptions);}static void LoggerLog(LogLevel logLevel, EventId eventId, ILogger logger, Exception exception, Func<TState, Exception, string> formatter, ref List<Exception> exceptions, in TState state){try{// 记录日志内容            logger.Log(logLevel, eventId, state, exception, formatter);}catch (Exception ex){if (exceptions == null){exceptions = new List<Exception>();}exceptions.Add(ex);}}}
    }
    

    ILoggerFactory/LoggerFactory

    namespace Microsoft.Extensions.Logging
    {// 创建 ILogger和注册LoggerProviderpublic interface ILoggerFactory : IDisposable{// 根据名称创建ILogger    ILogger CreateLogger(string categoryName);// 注册ILoggerProvidervoid AddProvider(ILoggerProvider provider);}
    }
    ........省略方法-私下研究......
    
    // LoggerFactory挑了几个关键方法进行说明
    // 创建Logger
    public ILogger CreateLogger(string categoryName)
    {if (CheckDisposed()){throw new ObjectDisposedException(nameof(LoggerFactory));}lock (_sync){if (!_loggers.TryGetValue(categoryName, out var logger)){// new一个Logger,这是LoggerFactory管理的Logger        logger = new Logger{// 根据注册的xxxLoggerProvider创建具体的xxxLogger// 并将其缓存到LoggerFactory创建的Logger对应的Loggers属性中                            Loggers = CreateLoggers(categoryName),};// 根据消息级别和作用域范围,赋值对应的MessageLoggers、ScopeLoggers(logger.MessageLoggers, logger.ScopeLoggers) = ApplyFilters(logger.Loggers);// 同时将创建出来的logger缓存在字典中_loggers[categoryName] = logger;}return logger;}
    }
    // 这个用于注册具体的xxxLoggerProvider
    public void AddProvider(ILoggerProvider provider)
    {if (CheckDisposed()){throw new ObjectDisposedException(nameof(LoggerFactory));}lock (_sync){// 将传入的provider封装了结构体进行缓存   AddProviderRegistration(provider, dispose: true);// 同时创建对应的logger,创建过程和上面一样foreach (var existingLogger in _loggers){var logger = existingLogger.Value;var loggerInformation = logger.Loggers;// 在原来基础上增加具体的xxxLoggervar newLoggerIndex = loggerInformation.Length;Array.Resize(ref loggerInformation, loggerInformation.Length + 1);loggerInformation[newLoggerIndex] = new LoggerInformation(provider, existingLogger.Key);logger.Loggers = loggerInformation;(logger.MessageLoggers, logger.ScopeLoggers) = ApplyFilters(logger.Loggers);}}
    }
    // 封装对应的xxxLoggerProvider,然后进行缓存
    private void AddProviderRegistration(ILoggerProvider provider, bool dispose)
    {// 先封装成结构体,然后在缓存,方便后续生命周期管理_providerRegistrations.Add(new ProviderRegistration{Provider = provider,ShouldDispose = dispose});// 判断是否继承了ISupportExternalScope if (provider is ISupportExternalScope supportsExternalScope){if (_scopeProvider == null){_scopeProvider = new LoggerExternalScopeProvider();}supportsExternalScope.SetScopeProvider(_scopeProvider);}
    }
    // 创建具体的xxxLogger
    private LoggerInformation[] CreateLoggers(string categoryName)
    {// 根据注册的xxxLoggerProvider个数初始化一个数组var loggers = new LoggerInformation[_providerRegistrations.Count];// 遍历注册的xxxLoggerProvider,创建具体的xxxLogger   for (var i = 0; i < _providerRegistrations.Count; i++){// 创建具体的xxxLogger    loggers[i] = new LoggerInformation(_providerRegistrations[i].Provider, categoryName);}return loggers;
    }
    ........省略方法-私下研究......
    
  • ILoggerProvider/xxxLoggerProvider

    namespace Microsoft.Extensions.Logging
    {public interface ILoggerProvider : IDisposable{// 根据名称创建对应的LoggerILogger CreateLogger(string categoryName);}
    }
    namespace Microsoft.Extensions.Logging
    {public interface ILoggerProvider : IDisposable{// 根据名称创建对应的LoggerILogger CreateLogger(string categoryName);}
    }
    
    namespace Microsoft.Extensions.Logging.Console
    {[ProviderAlias("Console")]public class ConsoleLoggerProvider : ILoggerProvider, ISupportExternalScope{// 支持Options动态监听  private readonly IOptionsMonitor<ConsoleLoggerOptions> _options;// 缓存对应的xxxLogger        private readonly ConcurrentDictionary<string, ConsoleLogger> _loggers;// 日志处理        private readonly ConsoleLoggerProcessor _messageQueue;private IDisposable _optionsReloadToken;private IExternalScopeProvider _scopeProvider = NullExternalScopeProvider.Instance;// 构造函数,初始化public ConsoleLoggerProvider(IOptionsMonitor<ConsoleLoggerOptions> options){_options = options;_loggers = new ConcurrentDictionary<string, ConsoleLogger>();ReloadLoggerOptions(options.CurrentValue);_optionsReloadToken = _options.OnChange(ReloadLoggerOptions);_messageQueue = new ConsoleLoggerProcessor();// 判断是否是Windows系统,因为即日至的方式不一样            if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)){  // 如果是windows_messageQueue.Console = new WindowsLogConsole();_messageQueue.ErrorConsole = new WindowsLogConsole(stdErr: true);}else{  // 如果是其他平台_messageQueue.Console = new AnsiLogConsole(new AnsiSystemConsole());_messageQueue.ErrorConsole = new AnsiLogConsole(new AnsiSystemConsole(stdErr: true));}}private void ReloadLoggerOptions(ConsoleLoggerOptions options){foreach (var logger in _loggers){logger.Value.Options = options;}}// 根据名称获取或创建对应xxxLoggerpublic ILogger CreateLogger(string name){// 根据名称获取,如果没有,则根据传入的委托方法进行创建        return _loggers.GetOrAdd(name, loggerName => new ConsoleLogger(name, _messageQueue){Options = _options.CurrentValue,ScopeProvider = _scopeProvider});}......省略一些方法,私下研究.......}
    }
    

    

想了想,这里就不一一针对不同目的地(比如Trace、EventLog)扒代码看了,不然说着说着就变成了代码解读了,如果有兴趣,可以私下照着以下思路去看看代码:

每一个目的地日志记录都会有一个实现xxxLoggerProvider和对应的记录器xxxLogger(真实记录日志内容),LoggerFactory创建的Logger(暴露给程序员使用的)包含了对应的具体的记录器,比如以写入日志控制台为例:

有一个ConsoleLoggerProvider的实现和对应的ConsoleLogger,ConsoleLoggerProvider负责通过名称创建对应的ConsoleLogger,而LoggerFactory创建出来的Logger就是包含已注册ConsoleLoggerProvider创建出来的ConsoleLogger;从而我们调用记录日志方法的时候,其实最终是调用ConsoleLoggerProvider创建的ConsoleLogger对象方法; 

总结

本来想着日志应该用的很频繁了,直接举例演示就OK了,但是写着写着,用的多不一定清除关键步骤,于是又扒了下代码,挑出了几个关键方法简单的说说,希望使用的小伙伴不困惑,深入研究就靠私下好好瞅瞅代码了;

下一节实例演示日志的使用、日志的作用域、集成第三方日志框架进行日志扩展.....

------------------------------------------------

一个被程序搞丑的帅小伙,关注"Code综艺圈",识别关注跟我一起学~~~

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

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

相关文章

Numpy中数组创建函数的辨析

首先推荐Numpy官方的教程&#xff0c;网址。 很多人会对数组创建函数的参数中什么时候要用括号np.zeros((2, 3))&#xff0c;什么时候不用括号np.eye(3, 5)感到疑惑&#xff0c;这里对它们统一进行梳理。&#xff08;按照官方文档的分类方法&#xff09; 1. 一维数组创建函数…

leetcode213. 打家劫舍 II

一:题目 二:上码 class Solution { public:/**思路:1.既然成环了,我们如果选取得一条偷取路径是从头开始得那么我们就不能偷取最后一个,那就不算最后一个偷取一遍2.同理我们也可以不算第一个 偷取一遍计算一次偷取得结果*/int rob(vector<int>& nums) {if(nums.size…

一文弄懂Numpy中ndarray的维度(dimension)/轴数(axis/axes)问题

Numpy库的核心是ndarray&#xff0c;实际上就是N维数组&#xff08;N-dimensional array&#xff09;&#xff0c;关于这个数据对象的详细介绍&#xff0c;参考官方文档最为合适。有一点要注意的是&#xff0c;ndarray的内置方法只有30多个&#xff0c;常用的如求平均值可以写a…

leetcode337. 打家劫舍 III

一:题目 二:上码 /*** Definition for a binary tree node.* struct TreeNode {* int val;* TreeNode *left;* TreeNode *right;* TreeNode() : val(0), left(nullptr), right(nullptr) {}* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}*…

asp.net core 从 3.1 到 5.0

asp.net core 从 3.1 到 5.0Intro就在前几天&#xff0c;微软宣布了 .NET5 发布了 RC1 版本&#xff0c;这也意味着 .NET5 的开发基本稳定了&#xff0c;正式发布之前&#xff0c;不会再新增新的 Feature&#xff0c;只会专注于修复 BUG 提高稳定性。对于开发者来说&#xff0c…

leetcoed123. 买卖股票的最佳时机 III

一&#xff1a;题目 二:上码 class Solution { public:/**思路:1.动态规划五步走1>:确定dp数组以及下标的含义因为题目给出至多完成两笔交易 那么我们一天的状态就有5种0 无操作1 第一次买入2 第一次卖出3 第二次买入4 第二次卖出dp[i][j] 表示的是在第i天 [0,4] 其中某个…

送福利 | 送书3本 ASP.NET Core 真机拆解

小编&#xff1a;最近.NET相关图书在多年沉寂后重新恢复&#xff0c;本书作者提供3本送给公众号粉丝&#xff0c;所以参与方式&#xff1a;文章下方留言&#xff0c;你可以聊聊.NET Core 这几年的发展给你的印象&#xff0c;你的感想&#xff0c;点赞最多的前5位获奖。活动截止…

异方差 的 BP检验 方法及原理详解

异方差 的 BP检验详解            文章目录 1. `BP`检验的步骤2. 场景示例步骤 ①步骤 ②BP检验,也称为Breusch-Pagan检验,是一种用于检验线性回归模型中异方差性(即误差项方差不恒定)的统计方法。该方法由Trevor S. Breusch和Adrian R. Pagan在1980年提出。 1.…

leetcode309. 最佳买卖股票时机含冷冻期

一&#xff1a;题目 二:上码 class Solution { public:/**思路:1.分析题意那么我们会有四种状态0 买入股票(或者说是之前就买入了股票但是也一直没有操作)1 卖出股票的状态一 两天前就卖出了股票 但是一直没有操作 2 卖出股票状态二 今天卖出股票3 冷冻期 持续一天2.动态规…

.NET Core 下使用 Exceptionless 记录日志

ExceptionLess是一套免费开源分布式系统日志收集框架&#xff0c;也是我无意中发现的&#xff0c;支持自己部署和平台托管的方式接入使用。ExceptionLess官网&#xff1a;https://exceptionless.comExceptionLess开源地址&#xff1a;https://github.com/exceptionless/Excepti…

你没有看错,爬网页数据,C# 也可以像 Jquery 那样

一&#xff1a;背景 1. 讲故事前段时间搞了一个地方性民生资讯号&#xff0c;资讯嘛&#xff0c;都是我抄你的&#xff0c;你抄官媒的&#xff0c;小市民都喜欢奇闻异事&#xff0c;所以就存在一个需求&#xff0c;如何去定向抓取奇闻异事的地方号上的新闻&#xff0c;其实做起…

leetcode300. 最长递增子序列

一:题目 二:上码 class Solution { public:/**思路:1.分析题意:我们在求取答案的过程中;我们的结果是动态的; 如果从某个数有一个递增序列 但是在这个数的后面又有一个数又可以是递增的 而且可能还比起长 2.动态规划五步走1>:确定dp数组的含义以及下标的含义dp[i] 表示的是…

链表基础概念与经典题目(Leetcode题解-Python语言)

所谓链表&#xff0c;就是由链节点元素组成的表&#xff0c;那什么是链节点呢&#xff1f;直接上定义&#xff1a; class ListNode:def __init__(self, val0, nextNone):self.val valself.next next很简单&#xff0c;链节点就是只记录自身的值 val&#xff0c;还有其指向的…

leetcode674. 最长连续递增序列

一:题目 二:上码 1:方法一贪心 class Solution { public:/**思路:1.贪心**/int findLengthOfLCIS(vector<int>& nums) {int ans 1;int count 1;for (int i 1; i < nums.size(); i) {if(nums[i] > nums[i-1]) {count;}else{count 1;//出现不连续的状态}if…

WPF 从 .net core 3.1 到 .net 5.0

WPF 从 .net core 3.1 到 .net 5.0Intro昨天更新了一个简单的 asp.net core webapi 项目&#xff0c;从 3.1 更新到了 5.0&#xff0c;今天更新一个 WPF 应用到 net 5.0项目文件更新首先项目文件中的 SDK 发生了变化&#xff0c;原来是 Microsoft.NET.Sdk.WindowsDesktop 更新后…

进击吧! Blazor !第三期 信息交互

Blazor 是一个 Web UI 框架&#xff0c;可通过 WebAssembly 在任意浏览器中运行 .Net 。Blazor 旨在简化快速的单页面 .Net 浏览器应用的构建过程&#xff0c;它虽然使用了诸如 CSS 和 HTML 之类的 Web 技术&#xff0c;但它使用 C&#xff03;语言和 Razor 语法代替 JavaScrip…

leetcode718. 最长重复子数组

一:题目 二:上码 class Solution { public:/**思路:1.分析题意:1 2 3 6 7 41 2 6 7 4 3这里我们先遇到的1 2 但后来我们又遇见了6 7 4 那么我们的要的答案就是动态变化的2.动态规划五步走1>:确定dp数组以及下标的含义dp[i][j] 表示 以下标i-1结尾的A数组和以下标…

.NET 5 的 Target Framework 详解[上篇]

❝作者&#xff1a;.NET Team翻译&#xff1a;精致码农-王亮原文&#xff1a;http://dwz.win/Q4v❞我们希望极大地简化开发人员必须在项目文件和 NuGet 包中使用的「TFM」 (Target Framework Name, 目标框架名称)。这包括合并 .NET 5 和 .NET Standard 的概念&#xff0c;同时仍…

leetcode1143. 最长公共子序列

一&#xff1a;题目 二:上码 class Solution { public:/**思路:1.分析题意这个子序列就是我们是可以不连续的字符组成的2.动态规划五步走1>:确定dp数组的含义以及下标的含义dp[i][j] 表示的是text1中[0,i-1]字符范围,text2中[0,j-1]的字符范围 的最长公共子序列这里我们取i…

JAVA 15发布,越来越像C# ?9月排名,C#增幅狠甩JAVA

2016年.NET Core首个正式版本问世&#xff0c;如今已发布到了.NET Core3.1&#xff0c;再有2个月.NET5也将如约而至&#xff0c;跨平台开发已经快5年。微软 .NET 程序管理总监 Scott 表示&#xff0c;.NET 5 是 .NET Framework 和 .NET Core 的未来&#xff0c;最终将成为一个统…