基于.NetCore3.1系列 —— 日志记录之初识Serilog

前言

对内置日志系统的整体实现进行了介绍之后,可以通过使用内置记录器来实现日志的输出路径。而在实际项目开发中,使用第三方日志框架(如:Log4Net、NLog、Loggr、Serilog、Sentry 等)来记录也是非常多的。首先一般基础的内置日志记录器在第三方日志框架中都有实现,然后第三方日志框架在功能上更加强大和丰富,能满足我们更多的项目分析和诊断的需求。

所以在这一篇中,我们将介绍第三方日志记录提供程序——Serilog

回顾

系统内置日志系列:

1. 基于.NetCore3.1系列 —— 日志记录之日志配置揭秘

2. 基于.NetCore3.1系列 —— 日志记录之日志核心要素揭秘

3. 基于.NetCore3.1系列 —— 日志记录之自定义日志组件

从之前学习的内置日志系统中,我们根据日志配置的方式了解到了通过配置的方式,可以有效的输出日志记录,方便我们查找发现问题。

而在进一步对内部运行的主要核心机制进行深入探究后发现了内置日志记录的几个核心要素,在日志工厂记录器(ILoggerFactory)中实现将日志记录提供器(ILoggerProvider)对象都可以集成到Logger对象组合中,这样的话,我们就可以通过基于ILoggerProvider自定义日志记录程序集成到Logger中,再创建写日志定义Ilogger,自定义日志记录器实现日志的输出方式,这样实现自定义日志记录工具。

在最后我们通过自定义的方式简单的实现了自定义日志组件,在这个基础上,我们可以根据具体的需求进行完善修改。当然了,我们也可以借用第三方日志框架组件程序进行使用。

说明

我们都知道日志记录在项目开发中或者生产环境中,都起到举足轻重的作用。因此,我们都会采用在项目加入第三方框架日志或自行封装日志记录来记录日志。

所以在这一篇中,我们会采用在项目中使用Serilog,目的不仅仅在于希望在用户使用之前发现代码中的BUG和错误,更多的是方便我们可以快速的查询生产环境的日志问题,深入的了解系统运行的表现。

Serilog的官方介绍中,我们可以发现 其框架是.net中的诊断日志库,可以在所有的.net平台上运行。支持结构化日志记录,对复杂、分布式、异步应用程序的支持非常出色。

Serilog是基于日志事件(log events),而不是日志消息(log message)。可以将日志事件格式化为控制台的可读文本或者将事件化为JSON格式。应用程序中的日志语句会创建LogEvent对象,而连接到管道的接收器(sinks)会知道如何记录它们。(接收器 包括各种终端、控制台、文本、SqlServer、ElasticSearch等等可用的列表)

结构化与非结构化之间的问题

对于日志的处理,在大部分情况下,会权衡是否对开发者的友好型以及对程序解析的方便性。在很多情况下,开发者可能只是想记录一段日志而已,所以可以会考虑简单的加上一行代码来以达到记录日志的目的,如(log.debug("Disk quota {0} exceeded by user {1}", quota, user);)当然了,日志的执行结构可能被存于文本文件或者数据库中。这样的日志从开发者的角度来说,清晰易懂,十分友好。

但是如果后续要使用程序取查找海量的的上述例子在某段时间内的特定用户,则很难高效率地完成这一要求,因为需要对每个日志进行字符串解析。因此,我们就需要寻求更快更方便的方式来查找记录。

非结构的日志

对自由格式文本的解析往往依赖于正则表达式,并且依赖于不变的文本。这会使解析自由格式的文本变得非常脆弱(即解析与代码中的确切文本紧密耦合)。

还考虑搜索/查找的情况,例如

SELECT text FROM logs WHERE text LIKE "Disk quota";

LIKE条件需要与每个text行值进行比较;再次,这在计算上是相对浪费的,尤其是在使用通配符时:

SELECT text FROM logs WHERE text LIKE "Disk %";

结构化的日志

使用结构化日志记录,与磁盘错误相关的日志消息在JSON中可能如下所示:

{ "level": "DEBUG", "user": "username", "error_type": "disk", "text": "Disk quota ... exceeded by user ..." }

这种结构的字段可以很容易地映射到例如 SQL表列名,这意味着查找可以更具体/更细粒度:

SELECT user, text FROM logs WHERE error_type = "disk";

您可以在希望经常搜索/查找其值的列上放置索引,只要您不对LIKE这些列值使用子句即可。您可以将日志消息细分为特定类别的内容越多,查找的对象就越有针对性。例如,除了error_type上面示例中的字段/列之外,您甚至可以设置为be "error_category": "disk", "error_type": "quota"或诸如此类。

结构越多,你的日志消息,通过解析/检索系统(如fluentdelasticsearchkibana),可以利用该结构,并以更快的速度和更低的CPU /内存执行任务。

总之这不仅与速度和效率有关,更重要的是使用结构化日志记录和“结构化查询”时,能以特定格式捕获以及呈现结构化日志,同时提供对开发者与程序友好的解析支持。可以更方便地以其为条件进行筛选,搜索结果的相关性将更高。如果没有这种搜索,那么在不同上下文中出现的任何单词都会给您带来大量无关的点击。

开始

为了更好的理解认识Serilog,我们这简单的创建一个新的项目来认识一下Serilog的使用。这里我们就简单的使用ConsoleDebug的方式来实现,后续有机会我们可以实现更多方式的接收器写入日志。

4.1 Serilog使用

4.1.1 安装依赖包

Serilog.AspNetCore : 基于AspNetCore框架整合的Serilog日志记录程序包,包含了Serilog基本库和控制台日志的实现。

当然了,你也可以直接安装Serilog 基本库,然后根据需要安装对应的拓展包。

说明:

  • Serilog.Extensions.Logging 包含了注入了Serilog的拓展方法。

  • Serilog.Sinks.Async 实现了日志异步收集。

  • Serilog.Sinks.Console 实现了控制台输出日志。

  • Serilog.Sinks.Debug 实现了调试台输出日志。

  • Serilog.Sinks.File 实现了文件输出日志。

4.1.2 配置Serilog

在应用程序中Program.cs文件中,配置Serilog记录,确保正确记录任何配置日志问题。

    public static void Main(string[] args){Log.Logger = new LoggerConfiguration().MinimumLevel.Debug().MinimumLevel.Override("Microsoft", LogEventLevel.Information).Enrich.FromLogContext().WriteTo.Console().CreateLogger();try{Log.Information("Starting web host");CreateHostBuilder(args).Build().Run();}catch (Exception ex){Log.Fatal(ex, "Host terminated unexpectedly");}finally{Log.CloseAndFlush();}}

然后,添加UseSerilog()CreateHostBuilder()中的通用主机中。

    public static IHostBuilder CreateHostBuilder(string[] args) =>Host.CreateDefaultBuilder(args) //从appsettings.json中读取配置。.UseSerilog() // <-- Add this line.ConfigureLogging((hostingContext, logging) =>{logging.ClearProviders(); //去掉默认添加的日志提供程序}).ConfigureWebHostDefaults(webBuilder =>{webBuilder.UseStartup<Startup>();});
}

最后,通过删除默认记录器的其余配置进行清理,从appsettings.json文件中删除Logging对应的配置部分。可以再使用根据Serilog的配置规则进行相应配置替换它。

"Serilog": {
"MinimumLevel": {
"Default": "Information",
"Override": {
"Microsoft": "Warning",
"System": "Warning"
}
}
}

4.1.3 提示

当在IIS下运行时候,要在Visual Studio输出窗口中查看Serilog输出日志的时候,需要将输出方式选择为 Web 服务器方式,输出窗口查看日志,或者使用WriteTo.Debug()替换记录器配置中的WriteTo.Console()

4.2 输出格式

4.2.1 文本格式

作为文本,它的格式如下:

[21:45:15 INF]  HTTP GET / responded 200 in 227.3253 ms

测试在控制台中输出如下:

上述事件格式中,可以看出由以下几个格式组成:

  • 事件发生时的时间戳[timestamp]

  • 描述何时应该捕获事件的级别[level]

  • 记录事件的消息[message]内容]

  • 描述事件的命名属性[properties]

  • 还可能有一个Exception对象

4.2.2 JSON格式

作为JSON格式,它的格式如下:

{"@t": "2020-08-27T13:59:44.6410761Z","@mt": "HTTP {RequestMethod} {RequestPath} responded {StatusCode} in {Elapsed:0.0000} ms","@r": ["224.5185"],"RequestMethod": "GET","RequestPath": "/","StatusCode": 200,"Elapsed": 224.5185,"RequestId": "0HLNPVG1HI42T:00000001","CorrelationId": null,"ConnectionId": "0HLNPVG1HI42T"
}

在写入日志文件中,根据Serilog的多种接收器的中(Console()、Debug()、File())等支持使用JSON写入日志记录,通过引用紧凑的JSON格式化类库[Serilog.Formatting.Compact]接收所有JSON格式的输出。

要编写以换行符分隔的JSON,请将CompactJsonFormatterRenderedCompactJsonFormatter传递到接收器配置方法,如下:

 .WriteTo.Console(new RenderedCompactJsonFormatter())或.WriteTo.Console(new CompactJsonFormatter())

运行这个程序将产生使用Serilog的紧凑格式JSON,并在对应的输出路径中生成换行符分隔的JSON流。

4.3 示例

4.3.1 安装依赖包

安装 Serilog.AspNetCore NuGet 包 ;

4.3.2 配置文件

appsettings.json配置文件添加 Serilog 配置,WriteTo 指定输出目标位置,它是一个数组类型,所以可以指定多个目标位置,这里暂时只指定输出到控制台:

{"Serilog": {"MinimumLevel": {"Default": "Debug"}}
}

4.3.3 设置配置信息

读取配置文件信息,设置配置信息

public static IConfiguration Configuration { get; } = new ConfigurationBuilder().SetBasePath(Directory.GetCurrentDirectory()).AddJsonFile("appsettings.json", optional: false, reloadOnChange: true).AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", optional: true).AddEnvironmentVariables().Build();

在main方法中,

 public static void Main(string[] args){Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(Configuration).Enrich.FromLogContext().WriteTo.Debug()   //输出路径.WriteTo.Console(outputTemplate: "[{Timestamp:HH:mm:ss} {Level:u3}] {Message:lj} {Properties:j}{NewLine}{Exception}")    //模板.CreateLogger();try{Log.Information("Starting web host");CreateHostBuilder(args).Build().Run();}catch (Exception ex){Log.Fatal(ex, "Host terminated unexpectedly");}finally{Log.CloseAndFlush();}}

Program.cs 添加 UseSerilog()

public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args).ConfigureWebHostDefaults(webBuilder =>{webBuilder.UseStartup<Startup>();}).UseSerilog();  //添加

4.3.4 设置请求管道

在 Startup.cs 的 中的Configure 请求管道中添加 UseSerilogRequestLogging

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{if (env.IsDevelopment()){app.UseDeveloperExceptionPage();}app.UseStaticFiles();app.UseSerilogRequestLogging();app.UseRouting();app.UseAuthorization();app.UseEndpoints(endpoints =>{endpoints.MapControllers();});
}

重要的是UseSerilogRequestLogging()调用应出现在诸如MVC之类的处理程序之前。中间件不会对管道中出现在它之前的组件进行时间或日志记录。通过将UseSerilogRequestLogging()放在它们之后,可以将其用于从日志中排除杂乱的处理程序,例如UseStaticFiles()。)

为了减少每个HTTP请求需要构造,传输和存储的日志事件的数量。在同一事件上具有许多属性还可以使请求详细信息和其他数据的关联更加容易。

默认情况下,以下请求信息将作为属性添加:

  • 请求方法

  • 请求路径

  • 状态码

  • 响应时间

您可以使用UseSerilogRequestLogging()上的选项回调来修改用于请求完成事件的消息模板,添加其他属性或更改事件级别:

app.UseSerilogRequestLogging(options =>
{// 自定义消息模板options.MessageTemplate = "Handled {RequestPath}";// 发出调试级别的事件,而不是默认事件options.GetLevel = (httpContext, elapsed, ex) => LogEventLevel.Debug;  //将其他属性附加到请求完成事件options.EnrichDiagnosticContext = (diagnosticContext, httpContext) =>{diagnosticContext.Set("RequestHost", httpContext.Request.Host.Value);diagnosticContext.Set("RequestScheme", httpContext.Request.Scheme);};
});

4.3.5 输出效果

由于日志总是输出一堆,我们不能快速的查找定位问题,其实 Serilog 输出的日志是非常简洁的,只有 HTTP GET ... 这一条,其他都是 AspNetCore 系统本身输出的,所以我们可以对输出的日志进行简化操作。

4.3.6 输出简化

为了使日志输出更简洁,我们可以设置不输出 AspNetCore Info 日志,只需在 Serilog配置节点中设置 AspNetCore 日志输出级别为 Warning

{"Serilog": {"MinimumLevel": {"Default": "Debug","Override": {"Microsoft": "Warning","System": "Warning"}}}
}

总结

    1. 本篇主要是对Serilog的说明,认识到是一个基于日志事件的而非日志消息的结构化日志类库。

    2. 简单的涉及对基础知识的认识以及使用,通过构建一个新的项目来实现Serilog的日志记录以及怎么使用这个框架。

    3. 在后续中如何结合这个日志类库引入项目中使用,以及对日志怎么存储和查询进行说明(会考虑 ELK存储采集分析 )。

    4. 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。

    5. 参考资料:官方简介 、Serilog文档、serilog-aspnetcore

    6. 搜索关注公众号【DotNet技术谷】--回复【serilog】,可获取本篇文章的源码。

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

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

相关文章

手把手教你剖析vue响应式原理,监听数据不再迷茫

Object.defineProperty实现vue响应式原理一、组件化基础1、“很久以前”的组件化&#xff08;1&#xff09;asp jsp php 时代&#xff08;2&#xff09;nodejs2、数据驱动视图&#xff08;MVVM&#xff0c;setState&#xff09;&#xff08;1&#xff09;数据驱动视图 - Vue MV…

leetcode:203. 移除链表元素(两种方法)

一:题目 二:上码 1:方法一&#xff1a;(虚拟一个首结点) class Solution { public:ListNode* removeElements(ListNode* head, int val) {//1.虚拟一个头结点 这样就不用单独处理了ListNode * virtuals new ListNode(0);//给其开辟个空间并且赋初值virtuals->next head…

面试中的网红虚拟DOM,你知多少呢?深入解读diff算法

深入浅出虚拟DOM和diff算法一、虚拟DOM&#xff08;Vitual DOM&#xff09;1、虚拟DOM&#xff08;Vitual DOM&#xff09;和diff的关系2、真实DOM的渲染过程3、虚拟DOM是什么&#xff1f;4、解决方案 - vdom&#xff08;1&#xff09;问题引出&#xff08;2&#xff09;vdom如…

Blazor带我重玩前端(六)

本文主要讨论Blazor事件内容&#xff0c;由于blazor事件部分很多&#xff0c;所以会分成上下两篇&#xff0c;本文为第二篇。双向绑定概述如图所示当点击单项绑定的时候&#xff0c;MyOnewayComponent里的属性值会发生变化&#xff0c;这种变化是单项的&#xff0c;仅仅只是本地…

leetcode707:设计链表(增删差)

一:题目 二:上码 class MyLinkedList { public://定义链表节点结构体struct LinkedNode {int val;LinkedNode* next;LinkedNode(int val):val(val), next(nullptr){}};// 初始化链表MyLinkedList() {node new LinkedNode(0); // 这里定义的头结点 是一个虚拟头结点&#xff0…

深入探究.Net Core Configuration读取配置的优先级

前言在之前的文章.Net Core Configuration源码探究一文中我们曾解读过Configuration的工作原理&#xff0c;也.Net Core Configuration Etcd数据源一文中探讨过为Configuration自定义数据源需要哪些操作。由于Configuration配置系统也是.Net Core的核心&#xff0c;其中也包含了…

TypeScript,从0到入门带你进入类型的世界

从0到入门进入TS的世界一、什么是TypeScript&#xff1f;1、编程语言的类型2、TypeScript究竟是什么&#xff1f;二、为什么要学习TypeScript&#xff1f;1、程序更容易理解2、效率更高3、更少的错误4、非常好的包容性5、一点小缺点三、typescript入门1、如何安装TypeScript2、…

编写第一个 .NET 微服务

介绍本文的目的是&#xff1a;通过创建一个返回列表的简单服务&#xff0c;并在 Docker 容器中运行该服务&#xff0c;让您熟悉使用 .NET 创建微服务的构建过程。安装 .NET SDK要开始构建 .NET 应用程序&#xff0c;首先下载并安装 .NET Core SDK&#xff08;软件开发工具包&am…

模板编译template的背后,究竟发生了什么事?带你了解template的纸短情长

解析模板编译template的背后发生了什么一、&#x1f4d1;初识模板编译1、vue组件中使用render代替template2、模板编译总结二、✏️感受模板编译的美1、with语法&#xff08;1&#xff09;例子展示&#x1f330;&#xff08;2&#xff09;知识点归纳三、&#x1f4c8;编译模板1…

leetcode24. 两两交换链表中的节点(思路+解析)

一:题目 二:思路 思路: 1.分析题意 这是相邻结点进行交换 如果是4个结点 那么1和2交换 3和4交换 如果是3个结点 那么就1和2进行交换 3不动 2.这里我们定义一个虚拟头节点方便操作&#xff0c;我们只需三步实现结点的交换 <1>:让虚拟结点指向第二个结点(进行交换的结点我…

把Autofac玩的和java Spring一样6

大家好&#xff0c;今天来介绍我开源的一个autofac.Annotation项目 源码&#xff1a;https://github.com/yuzd/Autofac.Annotation本项目是autofa的一个扩展组件&#xff0c;autofac是一个老牌的DI容器框架 &#xff0c;支持netframework和netcoreAnnotdation是注解的意思&…

『软件测试5』测开岗只要求会黑白盒测试?NO!还要学会性能测试!

浅谈软件测试中的性能测试一、&#x1f92a;性能测试概念1、为什么要有性能测试&#xff1f;2、性能测试是什么&#xff1f;3、性能测试的目的二、&#x1f910;性能测试指标1、响应时间2、吞吐量3、并发用户数4、TPS(Transaction Per Second)5、点击率6、资源利用率三、&#…

CLR的简单理解

CLR加载程序生成进程&#xff0c;一个进程中可以存在多个线程&#xff0c;当创建一个线程时&#xff0c;会分配1Mb的空间&#xff0c;也就是线程的栈空间&#xff0c;对应jvm的虚拟机堆栈&#xff0c;是线程执行过程中用到的工作内存。这片内存用于方法传递实参&#xff0c;并存…

『软件测试6』bug一两是小事,但安全漏洞是大事!

详解软件测试中的安全测试一、&#x1f4bf;安全测试概念1、安全测试概述2、安全测试与软件生命周期的关系3、常规测试与安全测试的不同&#xff08;1&#xff09;测试目标不同&#xff08;2&#xff09;假设条件不同&#xff08;3&#xff09;思考域不同&#xff08;4&#xf…

我们真的需要JWT吗?

JWT&#xff08;JSON Web Token&#xff09;是目前最流行的认证方案之一。博客园、各种技术公众号隔三差五就会推一篇JWT相关的文章&#xff0c;真的多如牛毛。但我对JWT有点困惑&#xff0c;今天写出来跟大家探讨探讨&#xff0c;不要喷哈。JWT原理本文默认读者已经对JWT有所了…

leetcode面试题 02.07. 链表相交

一:题目 二:思路 1.这道题我们是需要找到一个结点&#xff0c;并且从这个结点往后的结点都相等 2.我们需要将两个链表 右对齐 3.然后将长链表的指针移动到和短链表头结点相同的位置 4.接下来就是比较指针&#xff0c;当一个指针相同也就意味着往后的结点的数值也相等 三:上码…

详解队列在前端的应用,深剖JS中的事件循环Eventloop,再了解微任务和宏任务

队列在前端中的应用一、队列是什么二、应用场景三、前端与队列&#xff1a;事件循环与任务队列1、event loop2、JS如何执行3、event loop过程4、 DOM 事件和 event loop5、event loop 总结四、宏任务和微任务1、引例2、宏任务和微任务&#xff08;1&#xff09;常用的宏任务和微…

终于弄明白了 Singleton,Transient,Scoped 的作用域是如何实现的

一&#xff1a;背景1. 讲故事前几天有位朋友让我有时间分析一下 aspnetcore 中为什么向 ServiceCollection 中注入的 Class 可以做到 Singleton&#xff0c;Transient&#xff0c;Scoped&#xff0c;挺有意思&#xff0c;这篇就来聊一聊这一话题&#xff0c;自从 core 中有了 S…

leetcode142. 环形链表 II(暴力+双链表)

一:题目 二:思路 1.双指针 快慢指针(快指针一次一个结点&#xff0c;慢指针一次两个结点) 2.如果有环的话&#xff0c;那么快慢指针肯定会相遇 3.那么相遇的地点一定在环中 因为如果没有环的话慢指针是永远追不到快指针的 4.接下来就是判断出口在那里&#xff0c;我们定义一个…

动态 Restful API 生成

介绍通常在DDD开发架构中&#xff0c;我们写完服务层需要在控制器中写API&#xff0c;今天介绍一个组件 Plus.AutoApi 可以用它来动态生成 Restful 风格的 WebApi&#xff0c;不用写 Controller。快速使用在你的应用服务层中添加组件Install-Package Plus.AutoApi在 Startup 中…