本篇接着上一篇 [ASP.NET Core - 日志记录系统(一)] 往下讲,所以目录不是从 1 开始的。

2.4 日志提供程序

2.4.1 内置日志提供程序

ASP.NET Core 包括以下日志记录提供程序作为共享框架的一部分:

  • Console
  • Debug
  • EventSource
  • EventLog

除此之外,还有一些微软官方提供的,但是没有和 .NET Core 框架集成的提供程序,如 ApplicationInsightsAzureAppServicesFile 和 AzureAppServicesBlob ,这些在日常开发中使用的比较少,大家有兴趣的话可以自行了解一下。

  • 控制台

    Console 提供程序将输出记录到控制台。

    通用主机启动时就包含了控制台日志提供程序,使用它之后,我们记录的日志在调试过程中,可以在 VS 的 “调试输出” 窗口和 “ASP.NET Core Web 服务器” 窗口(非 IIS Press 启动) 看到。使用 dotnet run 运行应用时,可以在控制台窗口中看到。以 “Microsoft” 类别开头的日志来自 ASP.NET Core 框架代码。 ASP.NET Core 和应用程序代码使用相同的日志记录 API 和提供程序。

    控制台提供程序提供了多个 API,允许根据需要多输出格式、文字颜色等进行调整。

  • 调试

    Debug 提供程序使用 System.Diagnostics.Debug 类写入日志输出。 对 System.Diagnostics.Debug.WriteLine 的调用写入到 Debug 提供程序。

    在 Linux 上,Debug 提供程序日志位置取决于分发,并且可以是以下位置之一:

    • /var/log/message
    • /var/log/syslog
  • 事件来源

    EventSource 提供程序写入名称为 Microsoft-Extensions-Logging 的跨平台事件源。 在 Windows 上,提供程序使用的是 ETW。

    EventSource 日志提供程序记录的日志可以使用跨平台的 dotnet 追踪工具 dotnet-trace 来收集和跟踪。dotnet-trace 的按照和使用请参阅 dotnet-trace 诊断工具 - .NET CLI | Microsoft Learn 。

  • 事件日志

    仅在 Windows 系统下生效,可通过“事件查看器”进行日志查看。

    EventLog 提供程序将日志输出发送到 Windows 事件日志。 与其他提供程序不同,EventLog 提供程序不继承默认的非提供程序设置。 如果未指定 EventLog 日志设置,则它们默认为 LogLevel.Warning。若要记录低于 LogLevel.Warning 的事件,请显式设置日志级别。

    默认情况下,记录下来的事件日志一些基本参数如下:

    • LogName:“Application”
    • SourceName:“.NET Runtime”
    • MachineName:使用本地计算机名称。

    我们可以通过 AddEventLog 重载可以传入 EventLogSettings 来修改:

    var builder = WebApplication.CreateBuilder();
    builder.Logging.AddEventLog(eventLogSettings =>
    {
    eventLogSettings.SourceName = "MyLogs";
    });

上面已经讲到,在通过通用主机启动一个 .NET Core 应用时,会默认添加了 Console、Debug、EventSource 日志提供程序,如果运行平台是 Windows,还会添加 EventLog 日志提供程序。通过 ILoggingBuilder 我们可以重置并自定义日志提供程序的类型,这使得我们可以根据需要使用任何符合标准的日志提供程序。

如果是没有使用通用主机的非托管控制台应用,可以通过以下方式添加控制台日志提供程序:

using var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
});
ILogger logger = loggerFactory.CreateLogger<Program>();
logger.LogInformation("Example log message");

不止控制台日志提供程序,其他各种日志提供程序都是按照日志记录系统框架进行开发和集成。显然,仅这些内置日志提供程序并不能满足我们生产开发中的适应,例如缺少最基础且常用的文件日志提供程序,还有在分布式应用已经非常普遍的业界现状下,时常需要将日志写分布式日志系统中进行统一的管理和分析,这些是微软没有提供的,但是第三方都有成熟的按照 .NET Core 日志记录系统体系架构开发的实现,这些将在下面细讲。

2.4.2 源码解析

.NET Core 日志记录系统的使用非常简单方便,其扩展性非常强,上面的章节已经讲解了基本的配置和使用,最核心的实现在于 LogeerFactoryILogger, LogeerFactory 用于日志系统的配置,ILogger 用于日志记录。下面从框架源码的角度,解析一下日志记录系统的实现。

阅读一个框架源码,我们可以从其开放出来的 API 作为入口,这样能较容易地梳理出其设计思路和实现脉络。首先是日志配置这一块,我们在应用集成的时候,对日志系统的配置都是基于 ILoggingBuilder 的,当然通过上面章节的内容,大家都已经知道在我们 ILoggingBuilder 进行配置之前,通用主机已经进行了一些配置。

ILoggingBuilder 的实现类其实就只是保存了容器,其他各种可用的方法都是扩展方法,都是往容器之中添加配置。

在添加日志系统默认配置的时候,可以看到显示调用了 AddLogging() 方法,之后就是像我们自己在实际应用中对日志系统进行配置一样帮我们添加了一些默认配置。

AddLogging() 方法是扩展方法,在 LoggingServiceCollectionExtensions 类中,在这个方法中往容器注入了三个日志记录系统最关键的东西,分别是 LoggerFactoryLogger<> 和日志配置。

当我们从使用日志记录器的时候,要么就是从容器中解析,要么就是通过 LoggerFactory.CreateLogger() 方法创建,查看 Logger<> 类的实现,其内部其实也是通过 LoggerFactory 创建了 ILogger 实例,注意这里的 ILogger 是没有泛型的,最终我们使用的其实都是这一个没有泛型的。

LoggerFactory 在其初始化的时候,会从容器中解析出我们添加的日志记录提供程序以及和日记记录系统相关的配置信息,并将日志记录提供程序保存到集合中。

当调用 CreateLogger 方法时,会创建 Logger 实例,为其配置并应用过滤规则,并将其保存起来。

这里就引出了三个重要的内部实现 LoggerLoggerInformationMessageLoggerLogger 是上面讲到的我们最终实际使用的 ILogger 的实现类,它的构造函数中需要传入 LoggerInformation 数组,LoggerInformation 数组与与日志提供程序的数量对应。LoggerInformation 是一个结构体,是针对特定日志记录提供程序和日志类别的封装,在内部创建了特定于具体日志提供程序的日志记录器。

MessageLogger 是最终的日志信息书写的地方,它也是一个结构体,包含了规则过滤等内容,可以看到它的构造函数中传入了 LoggerInformationLogger 属性,也就是说最终也是使用特定于日志提供程序的日志记录器的。

最终返回的 MessageLogger 数组赋值给了 LoggerMessageLogger 数组不一定与日志记录提供程序的数量一样,应该有些日志记录提供程序在规则配置检查中可能跳过了。

Logger 类应用了装饰器模式,对多种日志记录提供程序的记录器进行了包装,提供了统一的日志记录 API,使得我们在使用时可以通过统一的入口将日志同时书写到不同的地方。当我们调用 Logger 实例的 Log 方法时,实际上是遍历了 MessageLogger 数组,通过具体的日志提供程序对应的日志记录器对当前日志级别进行检查,并进行最终的日志记录。

最终返回的 MessageLogger 数组赋值给了 LoggerMessageLogger 数组不一定与日志记录提供程序的数量一样,应该有些日志记录提供程序在规则配置检查中可能跳过了。

Logger 类应用了装饰器模式,对多种日志记录提供程序的记录器进行了包装,提供了统一的日志记录 API,使得我们在使用时可以通过统一的入口将日志同时书写到不同的地方。当我们调用 Logger 实例的 Log 方法时,实际上是遍历了 MessageLogger 数组,通过具体的日志提供程序对应的日志记录器对当前日志级别进行检查,并进行最终的日志记录。

以控制台提供程序为例,这里中间有很多代码,其实只是为了实现更好的扩展性和性能,可以先忽略不看,最终也只是返回了特定 ConsoleLogger

ConsoleLogger 中的 Log 方法最终是将日志信息放到队列中,再由队列处理器写到控制台中。

2.4.3 自定义日志提供程序

了解完 .NET Core 日志记录系统的整体实现逻辑之后,我们想实现一个自己的日志提供程序其实还是比较简单的,当然如果要像微软内置的日志记录提供程序,或者第三方成熟的日志框架那样,各种细节处理得很好,就稍微有些难度了。以下是一个简单的示例,模仿默认日志记录提供程序的的实现方式,将日志记录到文件中:

(1) 创建一个类库项目,并引入以下依赖包

Install-Package Microsoft.Extensions.Logging.Abstractions
Install-Package Microsoft.Extensions.Logging

(2) 首先是实现 ILogger 接口,提供我们的日志记录器

internal sealed class WeWantFileLogger : ILogger
{
private readonly object _sync = new object(); /// <summary>
/// 创建日志记录域
/// </summary>
/// <typeparam name="TState"></typeparam>
/// <param name="state"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
public IDisposable? BeginScope<TState>(TState state) where TState : notnull
{
// 由于不准备支持日志记录域功能,这里返回一个空实现
return NullScope.Instance;
} /// <summary>
/// 判断是否记录日志
/// </summary>
/// <param name="logLevel"></param>
/// <returns></returns>
public bool IsEnabled(LogLevel logLevel)
{
return logLevel != LogLevel.None;
} /// <summary>
/// 记录日志,这里简单的演示例子
/// 一个可用于正式环境的文件记录器还需考虑很多可扩展性、性能等因素
/// </summary>
/// <typeparam name="TState"></typeparam>
/// <param name="logLevel"></param>
/// <param name="eventId"></param>
/// <param name="state"></param>
/// <param name="exception"></param>
/// <param name="formatter"></param>
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
{
if (!IsEnabled(logLevel))
{
return;
} ThrowHelper.ThrowIfNull(formatter); string message = formatter(state, exception); if(string.IsNullOrEmpty(message))
{
return;
} message = $"{logLevel}: {message} { Environment.NewLine }"; if(exception != null)
{
message += Environment.NewLine + Environment.NewLine + exception;
} var filePath = Path.Combine(Directory.GetCurrentDirectory(), "log.txt");
lock (_sync)
{
System.IO.File.AppendAllText(filePath, message);
} }
}

其他相关的类如下:

internal sealed class NullScope : IDisposable
{
public static NullScope Instance = new NullScope(); private NullScope() { } public void Dispose()
{
}
} internal static partial class ThrowHelper
{
/// <summary>Throws an <see cref="ArgumentNullException"/> if <paramref name="argument"/> is null.</summary>
/// <param name="argument">The reference type argument to validate as non-null.</param>
/// <param name="paramName">The name of the parameter with which <paramref name="argument"/> corresponds.</param>
internal static void ThrowIfNull(
#if NETCOREAPP3_0_OR_GREATER
[NotNull]
#endif
object? argument,
[CallerArgumentExpression("argument")] string? paramName = null)
{
if (argument is null)
{
Throw(paramName);
}
} #if NETCOREAPP3_0_OR_GREATER
[DoesNotReturn]
#endif
private static void Throw(string? paramName) => throw new ArgumentNullException(paramName);
} [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)]
internal sealed class CallerArgumentExpressionAttribute : Attribute
{
public CallerArgumentExpressionAttribute(string parameterName)
{
ParameterName = parameterName;
} public string ParameterName { get; }
}

(3) 然后是实现 ILoggerProvider 接口,提供日志记录提供程序

[ProviderAlias("WeWantFile")]
public class WeWantFileLoggerProvider : ILoggerProvider
{
public ILogger CreateLogger(string categoryName)
{
return new WeWantFileLogger();
} public void Dispose()
{
}
}

(4) 提供相应的扩展方法

public static class WeWantFileLoggerFactoryExtensions
{
public static ILoggingBuilder AddWeWantFile(this ILoggingBuilder builder)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<ILoggerProvider, WeWantFileLoggerProvider>());
return builder;
}
}

(5) 在之前的项目中引用,并进行以下配置

// 清除默认的日志记录提供程序,添加自定义的日志记录提供程序
builder.Logging.ClearProviders();
builder.Logging.AddWeWantFile();

(6) 测试日志记录

调用之前测试用的 Web API 接口,代码如下:

// 各种日志API对应各种日志级别
// 断点
_logger.LogTrace("这是一个断点日志");
//调试
_logger.LogDebug("this is a debug.");
//信息
_logger.LogInformation("this is an info.");
//警告
_logger.LogWarning("this is a warning.");
//错误
_logger.LogError("this is an error.");
//当机
_logger.LogCritical("this is Critical");

可以看到项目文件夹中多了 log.txt文件,内容如下:

.NET Core 下的日志记录系统大概就介绍到这里,后面再继续介绍一下一些第三方日志框架,怎么将其集成到 .NET Core 框架中进行正式生产环境下的应用。

参考文章:

.NET Core 和 ASP.NET Core 中的日志记录 | Microsoft Learn

理解ASP.NET Core - 日志(Logging) - xiaoxiaotank - 博客园 (cnblogs.com)

ASP.NET Core 系列:

目录:ASP.NET Core 系列总结

上一篇:ASP.NET Core - 日志记录系统(一)

ASP.NET Core - 日志记录系统(二)的更多相关文章

  1. .NET 黑魔法 - asp.net core 日志系统

    asp.net core 里如何记录日志呢? 这要从asp.net core的依赖注入说起,在asp.net core里的依赖注入真是无所不在,各种面向切面的接口与事件. 好吧,来点干货. 首先,我们 ...

  2. ASP.NET Core学习之二 菜鸟踩坑

    对于像我这样没接触过core的人,坑还是比较多的,一些基础配置和以前差别很大,这里做下记录 一.Startup 1.注册服务 // This method gets called by the run ...

  3. ASP.NET Core 入门教程 10、ASP.NET Core 日志记录(NLog)入门

    一.前言 1.本教程主要内容 ASP.NET Core + 内置日志组件记录控制台日志 ASP.NET Core + NLog 按天记录本地日志 ASP.NET Core + NLog 将日志按自定义 ...

  4. ASP.NET Core 2.2 : 二十. Action的多数据返回格式处理机制

    上一章讲了系统如何将客户端提交的请求数据格式化处理成我们想要的格式并绑定到对应的参数,本章讲一下它的“逆过程”,如何将请求结果按照客户端想要的格式返回去. 一.常见的返回类型 以系统模板默认生成的Ho ...

  5. asp.net core 日志

    日志输出是应用程序必不可少的部分,log4net,nlog这些成熟的组件在之前的项目中被广泛使用,在asp.net core的项目中没有找到与之对应的log4net版本,nlog对core提供了很好的 ...

  6. 自定义ASP.NET Core日志中间件

    这个日志框架使用的是ASP.NET Core的NLog,用来记录每次请求信息和返回信息. 1.首先创建一个Web应用项目,我选择的是MVC模板: 2.使用NuGet添加Microsoft.Extens ...

  7. Asp.Net Core 进阶(二) —— 集成Log4net

    Asp.Net Core 支持适用于各种内置日志记录API,同时也支持其他第三方日志记录.在我们新建项目后,在Program 文件入口调用了CreateDefaultBuilder,该操作默认将添加以 ...

  8. ASP.NET Core系列(二):创建第一个.Net Core 项目

    前面讲过 .NET Core简介及开发环境安装,本章会讲一讲ASP.NET Core 2.0的项目结构,查看完整的ASP.NET Core系列文章:https://www.cnblogs.com/zh ...

  9. ASP.NET Core 2.2 : 二十六. 应用JWT进行用户认证

    本文将通过实际的例子来演示如何在ASP.NET Core中应用JWT进行用户认证以及Token的刷新方案(ASP.NET Core 系列目录) 一.什么是JWT? JWT(json web token ...

  10. ASP.NET Core 2.2 : 二十七. JWT与用户授权(细化到Action)

    上一章分享了如何在ASP.NET Core中应用JWT进行用户认证以及Token的刷新,本章继续进行下一步,用户授权.涉及到的例子也以上一章的为基础.(ASP.NET Core 系列目录) 一.概述 ...

随机推荐

  1. 【VMware VCF】使用 Offline Bundle Transfer Utility(OBTU)配置 VCF 脱机库。

    VMware Cloud Foundation 环境中,软件包仓库的来源支持两种方式,分别是 Online Depot 和 Offline Depot.第一种方式,是在 VCF 环境能够连接互联网的情 ...

  2. TOYOTA SYSTEMS Programming Contest 2024(AtCoder Beginner Contest 377) 补题记录(A-E)

    AtCoder Beginner Contest 377 A - Rearranging ABC 字符串有ABC三个字母即可. #include<bits/stdc++.h> using ...

  3. Go服务自动触发单元测试覆盖率

    一.用到的工具 Gitlab Jenkins Shell go test 二.实现原理 在gitlab上配置jenkins的webhook,当有代码变更时自动触发jenkins构建job,job内的s ...

  4. 鸿蒙Banner图一多适配不同屏幕

    认识一多 随着终端设备形态日益多样化,分布式技术逐渐打破单一硬件边界,一个应用或服务,可以在不同的硬件设备之间随意调用.互助共享,让用户享受无缝的全场景体验.而作为应用开发者,广泛的设备类型也能为应用 ...

  5. TP6 使用 nusoap为第三方webservice调用插件

    composer下载插件 composer require nusoap/nusoap use NuSoap\Client\Client; class Index extends BaseContro ...

  6. Java 面试用什么项目?全是商场秒杀 RPC,我吐了

    看了几百份简历,真的超过 90% 的小伙伴的项目是商城.RPC.秒杀.论坛.外卖.点评等等烂大街的项目,人人都知道这些项目烂大街了,但大部分同学还是得硬着头皮做,没办法,网络上能找到的.教程比较完善的 ...

  7. PS命令显示进程数,清理僵尸进程

    显示系统上所有进程的进程树 ps -ef --forest 显示给定进程的进程树 -C:Select by command name.这个命令会把name指定进程的所有子进程显示出来 [root@lo ...

  8. Linux中find命令详解

    find命令 find 命令用于查找文件或目录 语法格式: find ./ -type f -name '文件名' 参数依次是:find命令,这里的./指的是当前路径,-type是选择文件类型,文件类 ...

  9. JVM性能优化, Part 3 —— 垃圾回收

    ImportNew注:本文是JVM性能优化 系列-第3篇-<JVM性能优化, Part 3 -- 垃圾回收> 第一篇 <JVM性能优化, Part 1 ―― JVM简介 > 第 ...

  10. VUE 改成history 模式 刷新404 的问题

    vue 路由的URL有两种模式,一种是 hash,一种是history ,history 模式更好看一些. 在使用hisory模式时,由于地址并不是真实存在,那么在刷新的情况下,这个会报404错误. ...