ASP.NET Core - 日志记录系统(二)

news2025/1/14 22:37:50

ASP.NET Core - 日志记录系统(二)

    • 2.4 日志提供程序
      • 2.4.1 内置日志提供程序
      • 2.4.2 源码解析

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

2.4 日志提供程序

2.4.1 内置日志提供程序

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

  • Console
  • Debug
  • EventSource
  • EventLog

除此之外,还有一些微软官方提供的,但是没有和 .NET Core 框架集成的提供程序,如 ApplicationInsights 、AzureAppServicesFile 和 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 数组,通过具体的日志提供程序对应的日志记录器对当前日志级别进行检查,并进行最终的日志记录。

在这里插入图片描述

以控制台提供程序为例,这里中间有很多代码,其实只是为了实现更好的扩展性和性能,可以先忽略不看,最终也只是返回了特定 ConsoleLogger
在这里插入图片描述
ConsoleLogger 中的 Log 方法最终是将日志信息放到队列中,再由队列处理器写到控制台中。

在这里插入图片描述
了解完 .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)

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

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

相关文章

无源器件-电容

电容器件的参数 基本概念由中学大学物理或电路分析内容获得&#xff0c;此处不做过多分析。 电容的产量占全球电子元器件产品的40%以上。 单位&#xff1a;法拉 F&#xff1b;1F10^6uF&#xff1b;电路中常见的104电容就是10*10^4pF100nF0.1uF C为电容&#xff0c;Rp为绝缘电…

云平台一键部署【Video-Background-Removal】视频换背景,无任何限制,随意换

Video-Background-Removal 是一款革命性的视频背景替换工具&#xff0c;旨在让用户轻松实现视频背景的快速更换。无论你是专业创作者还是普通用户&#xff0c;这款软件都能让你在几秒钟内改变背景&#xff0c;完全消除限制&#xff0c;随心所欲&#xff0c;随时随地想换就换&am…

HCIP笔记1--IP路由基础回顾、BFD单臂回声、OSPF基础

1. 路由基础回顾 概念 AS(Aotonomous System): 自治系统&#xff0c;由同一机构管理的路由器集合。LAN(Local Area Network): 局域网&#xff0c;用户所使用的网络WAN(Wideless Area Network): 广域网&#xff0c;运营商网络广播域&#xff1a;一个广播帧能在网络中到达的所有…

【Linux网络编程】数据链路层 | MAC帧 | ARP协议

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站 &#x1f308;个人主页&#xff1a; 南桥几晴秋 &#x1f308;C专栏&#xff1a; 南桥谈C &#x1f308;C语言专栏&#xff1a; C语言学习系…

微软与腾讯技术交锋,TRELLIS引领3D生成领域多格式支持新方向

去年 11 月&#xff0c;腾讯推出 Hunyuan3D 生成模型&#xff0c;是业界首个同时支持文字和图像生成 3D 的开源大模型。紧接着不到一个月&#xff0c;微软便发布了全新框架 TRELLIS&#xff0c;加入 3D 资产生成领域的竞争中。TRELLIS 支持多格式输出&#xff0c;包括辐射场、3…

【爬虫】单个网站链接爬取文献数据:标题、摘要、作者等信息

源码链接&#xff1a; https://github.com/Niceeggplant/Single—Site-Crawler.git 一、项目概述 从指定网页中提取文章关键信息的工具。通过输入文章的 URL&#xff0c;程序将自动抓取网页内容 二、技术选型与原理 requests 库&#xff1a;这是 Python 中用于发送 HTTP 请求…

设计模式-结构型-组合模式

1. 什么是组合模式&#xff1f; 组合模式&#xff08;Composite Pattern&#xff09; 是一种结构型设计模式&#xff0c;它允许将对象组合成树形结构来表示“部分-整体”的层次结构。组合模式使得客户端对单个对象和组合对象的使用具有一致性。换句话说&#xff0c;组合模式允…

js:正则表达式

目录 正则表达式的语法 定义 检测 检索 元字符 边界符 量词 字符类 表单判断案例 修饰符 过滤敏感词 正则表达式是一种用于匹配和操作文本的强大工具&#xff0c;它是由一系列字符和特殊字符组成的模式&#xff0c;用于描述要匹配的文本字符组合模式 正则表达式是一…

PHP数据过滤函数详解:filter_var、filter_input、filter_has_var等函数的数据过滤技巧

PHP数据过滤函数详解&#xff1a;filter_var、filter_input、filter_has_var等函数的数据过滤技巧&#xff0c;需要具体代码示例 在 Web 开发中&#xff0c;数据过滤是非常重要的一环。过滤用户输入的数据可以保护我们的应用程序免受潜在的安全威胁。PHP 提供了一系列强大的数…

QT跨平台应用程序开发框架(1)—— 环境搭建

目录 一&#xff0c;关于QT 二&#xff0c;关于应用程序框架 三&#xff0c;环境搭建 3.1 预备 3.2 下载Qt SDK 3.3 安装Qt SDK 3.4 配置环境变量 3.5 认识一些重要工具 四&#xff0c;Qt Creator 的基本使用 4.1 创建项目 4.2 代码解释 一&#xff0c;关于QT 互联网…

【MySQL】基础架构分析

考察频率难度40%⭐⭐⭐⭐ 这道题在面试时的出现频率其实并不高&#xff0c;最起码对于笔者来说是没有遇到过。那为什么还是选择把这个问题作为 MySQL 八股文系列的第一个呢&#xff1f;其实原因也挺简单的&#xff0c;还是老规矩&#xff0c;先通过一个问题把整个知识框架来一…

【华为云开发者学堂】基于华为云 CodeArts CCE 开发微服务电商平台

实验目的 通过完成本实验&#xff0c;在 CodeArts 平台完成基于微服务的应用开发&#xff0c;构建和部署。 ● 理解微服务应用架构和微服务模块组件 ● 掌握 CCE 平台创建基于公共镜像的应用的操作 ● 掌握 CodeArts 平台编译构建微服务应用的操作 ● 掌握 CodeArts 平台部署微…

Elasticsearch ES|QL 地理空间索引加入纽约犯罪地图

可以根据地理空间数据连接两个索引。在本教程中&#xff0c;我将向你展示如何通过混合邻里多边形和 GPS 犯罪事件坐标来创建纽约市的犯罪地图。 安装 如果你还没有安装好自己的 Elasticsearch 及 Kibana 的话&#xff0c;请参考如下的链接来进行安装。 如何在 Linux&#xff0…

P10打卡——pytorch实现车牌识别

&#x1f368; 本文为&#x1f517;365天深度学习训练营中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 1.检查GPU from torchvision.transforms import transforms from torch.utils.data import DataLoader from torchvision import datasets import torchvisio…

GitCode G-Star 光引计划终审前十名获奖项目公示

在技术的浩瀚星空中&#xff0c;GitCode 平台上的 G-Star 项目熠熠生辉。如今&#xff0c;“光引计划” 已圆满落幕&#xff0c;众多 G-Star 项目作者&#xff0c;一同分享项目在 GitCode 平台托管的宝贵体验&#xff0c;并深入挖掘平台的多样玩法。 众多投稿纷至沓来&#xf…

【pycharm】远程部署失败,查看日志

pycharm 远程部署失败,查看日志 远程一直失败,gateway超时会还知道拉取一份日志: 在./root 下 发现了崩溃日志,启动崩溃了,导致backend一直无法启动。win11就是一直在connect到ubuntu的后端pycharm。。gateway 拉取的日志 我上传的linux版本的pycharm暂时存在dist目录下,…

浅谈云计算02 | 云计算模式的演进

云计算计算模式的演进 一、云计算计算模式的起源追溯1.2 个人计算机与桌面计算 二、云计算计算模式的发展阶段2.1 效用计算的出现2.2 客户机/服务器模式2.3 集群计算2.4 服务计算2.5 分布式计算2.6 网格计算 三、云计算计算模式的成熟与多元化3.1 主流云计算服务模式的确立3.1.…

Vue 学习之旅:从基础到实践(vue快速上手+插值表达式+指令上)

Vue 学习之旅&#xff1a;从基础到实践 文章目录 Vue 学习之旅&#xff1a;从基础到实践一、Vue 简介二、创建 Vue 实例与插值表达式&#xff08;一&#xff09;创建 Vue 实例步骤&#xff08;二&#xff09;插值表达式 三、Vue 核心特性 - 响应式四、Vue 指令&#xff08;一&a…

PMP–一、二、三模、冲刺–分类–7.成本管理

文章目录 技巧一模7.成本管理--4.控制成本--数据分析--挣值分析--进度绩效指数&#xff08;SPI&#xff09;是测量进度效率的一种指标&#xff0c;表示为挣值与计划价值之比&#xff0c;反映了项目团队完成工作的效率。 当 SPI小于 1.0 时&#xff0c;说明已完成的工作量未达到…

字符串 (算法十一)

简介 没有固定题型&#xff0c;内容很杂&#xff0c;可以学习下string接口与相关操作 1.最长公共前缀 link&#xff1a; 解法一&#xff1a;两两比较 code class Solution { public:string longestCommonPrefix(vector<string>& strs) {// 两两比较string ans …