前言

鲁迅都说:没有日志的系统不能上线(鲁迅说:这句我没说过,但是在理)!日志对于一个系统而言,特别重要,不管是用于事务审计,还是用于系统排错,还是用于安全追踪.....都扮演了很重要的角色;之前有很多第三方的日志框架也很给力,如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();
// 注册ILoggerFactory
services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>());
// 注册ILogger
services.TryAdd(ServiceDescriptor.Singleton(typeof(ILogger<>), typeof(Logger<>)));
// 注册日志级别过滤,并默认设置级别为Information
services.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和注册LoggerProvider
    public interface ILoggerFactory : IDisposable
    {
    // 根据名称创建ILogger
    ILogger CreateLogger(string categoryName);
    // 注册ILoggerProvider
    void 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;
    // 在原来基础上增加具体的xxxLogger
    var 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
    {
    // 根据名称创建对应的Logger
    ILogger 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;
    }
    }
    // 根据名称获取或创建对应xxxLogger
    public 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综艺圈",识别关注跟我一起学~~~

跟我一起学.NetCore之日志(Log)模型核心的更多相关文章

  1. 跟我一起学.NetCore之日志作用域及第三方日志框架扩展

    前言 上一节对日志的部分核心类型进行简单的剖析,相信现在再使用日志的时候,应该大概知道怎么一回事了,比如记录器是怎么来的,是如何将日志内容写入到不同目的地的等:当然还有很多细节没深入讲解,抽时间小伙伴 ...

  2. 跟我一起学.NetCore之文件系统应用及核心浅析

    前言 在开发过程中,肯定避免不了读取文件操作,比如读取配置文件.上传和下载文件.Web中html.js.css.图片等静态资源的访问:在配置文件读取章节中有说到,针对不同配置源数据读取由对应的ICon ...

  3. 跟我一起学.NetCore之MVC过滤器,这篇看完走路可以仰着头走

    前言 MVC过滤器在之前Asp.Net的时候就已经广泛使用啦,不管是面试还是工作,总有一个考点或是需求涉及到,可以毫不疑问的说,这个技术点是非常重要的: 在之前参与的面试中,得知很多小伙伴只知道有一两 ...

  4. 如何正确使用日志Log

    title: 如何正确使用日志Log date: 2015-01-08 12:54:46 categories: [Python] tags: [Python,log] --- 文章首发地址:http ...

  5. 一件关于数据库日志log的无聊事情

    为何说是无聊的记录呢? 因为事先把问题想复杂了,事后发现的时候觉得更是无聊的行为.还是写下来,毕竟很少弄这么无聊的事情. 事情起因是需要给服务器做性能基数(baseline),用sqldiag 提取了 ...

  6. Expo大作战(六)--expo开发模式,expo中exp命令行工具,expo中如何查看日志log,expo中的调试方式

    简要:本系列文章讲会对expo进行全面的介绍,本人从2017年6月份接触expo以来,对expo的研究断断续续,一路走来将近10个月,废话不多说,接下来你看到内容,将全部来与官网 我猜去全部机翻+个人 ...

  7. C# 分析 IIS 日志(Log)

    由于最近又要对 IIS日志 (Log) 分析,以便得出各个搜索引擎每日抓取的频率,所以这两天一直在尝试各个办法来分析 IIS 日志 (Log),其中尝试过:导入数据库.Log parser.Powse ...

  8. 跟我一起学.NetCore之选项(Options)核心类型简介

    前言 .NetCore中提供的选项框架,我把其理解为配置组,主要是将服务中可供配置的项提取出来,封装成一个类型:从而服务可根据应用场景进行相关配置项的设置来满足需求,其中使用了依赖注入的形式,使得更加 ...

  9. 跟我一起学.NetCore之静态文件处理的那些事

    前言 如今前后端分离开发模式如火如荼,开发职责更加分明(当然前后端一起搞的模式也没有完全褪去):而对于每个公司产品实施来说,部署模式会稍有差别,有的会单独将前端文件部署为一个站点,有的会将前端文件和后 ...

随机推荐

  1. format 进阶

    '''format(数字,str(算术式)+"d或者f") d 表示 int f 表示 float ''' format(5,str(2*4)+"d") '' ...

  2. 自制廉价的LED+LCD型投影仪

    文档标识符:PROJECTOR_T-D-P6 作者:DLHC 最后修改日期:2020.7.30 本文链接:https://www.cnblogs.com/DLHC-TECH/p/PROJECTOR_T ...

  3. PHP date_get_last_errors() 函数

    ------------恢复内容开始------------ 实例 返回解析日期字符串时的警告和错误: <?phpdate_create("gyuiyiuyui%&&/ ...

  4. Hadoop学习之常用输入输出格式总结

    目的 总结一下常用的输入输出格式. 输入格式 Hadoop可以处理很多不同种类的输入格式,从一般的文本文件到数据库. 开局一张UML类图,涵盖常用InputFormat类的继承关系与各自的重要方法(已 ...

  5. Python初学者的自我修养,找到自己的方向

    今天是 Python专题 的第22篇文章,原本今天是准备和大家继续Python当中多线程的使用的相关内容.然而前两天有一个读者在后台问我,学习Python有哪些适合新手入门的小项目推荐,所以今天这篇临 ...

  6. 方法解析之Method与ConstMethod介绍

    HotSpot通过Method与ConstMethod来保存方法元信息. 1.Method Method没有子类,定义在method.hpp文件中,其类继承关系如下图: Method用于表示一个Jav ...

  7. Python实现迪杰斯特拉算法

    首先我采用邻接矩阵法来表示图(有向图无向图皆可) 图的定义如下: class Graph: def __init__(self, arcs=[]): self.vexs = [] self.arcs ...

  8. vue scss 样式穿透

    使用2个style的方式不够优雅,可以使用下面方式做样式穿透 .normal-field /deep/ .el-form-item { margin-bottom: 0px; } .normal-fi ...

  9. MyKTV系统项目的感想

    不粉身碎骨,何以脱胎换骨! 3月11号,我们迎来S1的尾巴.这期间有温暖,默契,有项目.一切刚刚好.刚刚正式接到KTV这个微微型的项目的时候,还是很害怕的,虽然老师在前两天就已经提到也讲到,KTV系统 ...

  10. 【python接口自动化】- 使用requests库发送http请求

    前言:什么是Requests ?Requests 是⽤Python语⾔编写,基于urllib,采⽤Apache2 Licensed开源协议的 HTTP 库.它⽐ urllib 更加⽅便,可以节约我们⼤ ...