【.NET】Logging 库探索(一):基础接口

Logging 属于 .NET 官方扩展库的一部分,因此,它不算第三方库。应某大伙伴要求,老周出一系列介绍 Logging 的水文。老周向来乐于分享技术,啥主题不重要,只要老周会的东西就行;不会的东西,老周就不误人子弟。

Logging 库嘛,当然是用来记录日志的。从功能上而言,它不会太复杂。为了照顾入门的大伙伴,老周先说说库的结构。

第一句话:它有两个基础包:Microsoft.Extensions.Logging.Abstractions;Microsoft.Extensions.Logging。这些包中只是定义一些公共的,基本的类型。即抽象层。

第二句话:基础包不包含真正的日志功能,不同类型的日志分散到各种 Provider 包中。如 Microsoft.Extensions.Logging.Console 用于控制台输出日志;再如 Microsoft.Extensions.Logging.Debug 包将日志输出到“调试”窗口。属于实现层。

第三句话:你可以实现自己的日志 Provider,如果把日志输出到数据库,输出到文本文件,输出到串口,输出为音频(很新鲜吧,日志还能输出为音频,其实就是 TTS),等等。

你只需把上面三句话在心中默念三遍即可,一定要像念“阿弥陀佛”那样虔诚。然后,你可以忘了,不影响你使用它。

一、Logging 的前世今生

相信很多人也和老周一样,最早是在 ASP.NET Core 项目中知道 Logging 库的。是的,一开始它就是用在 ASP.NET Core 中的,你只要在配置服务容器时来一句 AddLogging 就配置好了,至于里面有啥,不知道。

不仅仅 Logging 库,其实用于配置应用程序的 Configuration 和 Options 库一开始也是为 ASP.NET Core 服务的。后来,官方团队把 Microsoft.Extensions.**** 库都分离出来,成为了各种 .NET 扩展,以补充主体功能。这使得服务 Hosting、服务容器、配置、选项、日志、缓存、对象池等等一大堆东西可以用在各种 .NET 项目中。

二、如何在服务容器外使用 Logging

有服务容器的情况下,你只要调用一下 AddLogging 方法,再在委托中用 LoggingBuilder 添加一下日志 Provider 就完事了。在没有使用容器的时候咋办呢?下面老周举个例子。

1、新建一个控制台项目,不要复杂的,控制台就行了。而且咱们用的就是输出到控制台的日志。

2、引用以下两个包。方法你随便选,用 VS 的 nuget 包管理器也行,用 dotnet add package 命令也行,直接编辑项目文件也行。

<ItemGroup>
    <PackageReference Include="Microsoft.Extensions.Logging.Console" Version="10.0.12" />
    <PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.12" />
</ItemGroup>

Microsoft.Extensions.Logging 可以不引用,只引用 *.Console 就行了,因为存在依赖关系,nuget 会自己查找的。

3、找到 Main 方法(用顶层语句可以不写 Main),直接开写。

static void Main(string[] args)
{
    // 先建工厂
    ILoggerFactory logfac = LoggerFactory.Create(loggingBuilder =>
    {
        // 购买生产设备——Console
        loggingBuilder.AddConsole();
    });

    // 生产一个 Logger 用一下
    ILogger logger = logfac.CreateLogger("大冬菇OA系统");

    // 现在可以写日志了
    logger.LogInformation("上半年卖出设备5台");                   // 一般信息
    logger.LogWarning("售出后接到大量用户投诉");                   // 警告,可能出事了
    logger.LogError("5台设备中有4台存在质量问题,要返厂");          // 错误,出大事了
    logger.LogCritical("下半年公司资金链断了");                   // 关键消息,重大险情

    // See you la la
}

首先,需要一个工厂类 LoggerFactory,它的功能是创建 Logger。

Logger 的功能单一,就是记录日志。完了。就这么简单。

LogXXX 是扩展方法,方便写日志时调用。如 LogInformation 表示输出的日志是一般的消息,LogWarning 方法表示输出的消息是警告。这些 Information、Warning、Error、Critical 叫日志级别(或日志等级)。通俗地说就是消息的严重程度。

记录的日志会长这个样子。

image

现在,你只要会写上面的代码就行(别交给 AI 去写,那样你学不会的),一些细节不懂不用管,老周后面会介绍的。

三、重要的接口

 有几个接口类型很重要,它们共同构筑了 Logging 库的主体骨架和规范。

 1、ILoggerProvider:如果要自定义的日志输出,那这个接口就很重要。该接口只实现一个方法:

ILogger CreateLogger(string categoryName);

ILogger 接口的实现类用于写日志,咱们稍后再介绍。categoryName 参数表示日志的类别。说人话就是,方便人们知道这条日志是哪来的,谁写入的,跟哪个功能模块有关。这样才方便跟踪。

总的来说,ILoggerProvider 的作用是创建 Logger。

2、ILogger:这个接口的实现就是写日志的核心逻辑。

 BeginScope 方法:这个东西主要在连续输出多条日志时用得上,它返回的对象实现 IDisposable 接口,可在 using 语句块中使用,用完释放。也就是在一个作用域内输出N条日志时可以共享这些日志之间的一些数据(上下文)。

想看效果?来,给大伙举个例子。

var fac = sp.GetRequiredService<ILoggerFactory>();
var logger = fac.CreateLogger("Hhahaha");
using (var w = logger.BeginScope<string>("HR模块"))
{
    logger.LogInformation("招人啦");
    logger.LogWarning("招不到人啦");
}
using (var k = logger.BeginScope<string>("采购模块"))
{
    logger.LogInformation("9-12:购入200台PDA");
    logger.LogCritical("9-13:其中有150台货不对版");
}

效果如下:

image

上述代码中,“HR模块”、“采购模块”就是上下文数据。

 

IsEnabled 方法:决定这条日志要不要记录。返回 false 表示不记录这条日志(禁用日志)。

Log 方法:真正写日志的逻辑在这里。如把日志写入文本文件。

3、ILoggerFactory:这个一般咱们不需要实现,框架默认实现类为 LoggerFactory。此接口也存在 CreateLogger 方法,但与 ILoggerProvider 接口不同。LoggerFactory 内部维护一个 ILoggerProvider 列表,在创建 Logger 会调用每个 Provider 的 CreateLogger 方法。比如,你添加了两种日志:输出到控制台(Console)和输出到“调试”窗口(Debug)。LoggerFactory 会维护两个 Provider,当创建 Logger 时,这两个 Provider 会创建各个的 Logger,然后把多个 Logger 套娃,封装进一个 Logger中。这个套了娃的 Logger 就是返回给你用的,当你调用这个 Logger 写日志时,它就 for 循环调用里面套的 Logger 崽。比如,你用这个 Looger 写了一条错误信息,它里面有 Console 和 Debug 两个 Logger,于是循环调用这两个 Logger 的相关方法。这使得你这条错误信息会同时写到控制台和“调试”窗口中。

于是,你会疑惑:如果我启用多种日志,是不是会影响性能?当然会有点的,所以日志内容要简短,生成的逻辑也别太复杂。在实现自定义的日志 Provider 时,最好用一个队列先缓存日志,再在多线程上提取队列来输出日志。这样输出日志时能减少对其他代码的影响。

4、ILogger<T>:这个接口其实与 ILogger 一样,而且是从 ILogger 派生。它只是把日志类别类型化罢了,即将某个类型的名称作为日志类别(Category)。它有个默认实现类 Logger<T>,而且它们自动注册到服务容器中。Logger<T> 通过构造函数注入 ILoggerFactory 服务,在类的内部套了个娃——内部维护一个 ILogger 类型的字段。由 LoggerFactory 负责创建 Logger,赋值给内部的 ILogger 字段。所以,Logger<T> 的公共方法实际上是调用了内部 ILogger 字段的方法。结论:咱们一般不需要自己实现 ILogger<T> 接口。

5、ILoggingBuilder:内部实现,不公开。此接口主要是调用它的扩展方法,作用是配置与日志有关的东西。如调用 AddConsole 方法来添加控制台的日志功能。还可以调用 AddFilter 方法配置日志过滤——就是控制哪些日志会记录,哪些日志忽略。如果使用配置文件(如 JSON 文件),可以调用 AddConfiguration 方法指定从哪个节点开始读取配置。例如,ASP.NET Core Host 在初始化时会添加对 Logging 节点。

services.AddLogging(logging =>
{
    logging.AddConfiguration(configuration.GetSection("Logging"));
    logging.AddSimpleConsole();

    logging.Configure(options =>
    {
        ……
    });
});

现在,你就明白为啥 appsettings.json 文件中默认会有个 Logging 节点了吧。

最后,老周画了一张没啥艺术性的图,来描述各个接口之间的关系。 

好了,本篇水文只是个开胃菜,后面老周会介绍日志过滤器、配置,以及自定义日志的内容。

 

posted @ 2026-09-26 11:56  东邪独孤  阅读(25)  评论(0)    收藏  举报