基于.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"
或诸如此类。结构越多,你的日志消息,通过解析/检索系统(如
fluentd
,elasticsearch
,kibana
),可以利用该结构,并以更快的速度和更低的CPU /内存执行任务。总之这不仅与速度和效率有关,更重要的是使用结构化日志记录和“结构化查询”时,能以特定格式捕获以及呈现结构化日志,同时提供对开发者与程序友好的解析支持。可以更方便地以其为条件进行筛选,搜索结果的相关性将更高。如果没有这种搜索,那么在不同上下文中出现的任何单词都会给您带来大量无关的点击。
四、开始
为了更好的理解认识Serilog,我们这简单的创建一个新的项目来认识一下Serilog的使用。这里我们就简单的使用Console
和Debug
的方式来实现,后续有机会我们可以实现更多方式的接收器写入日志。
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,请将CompactJsonFormatter
或RenderedCompactJsonFormatter
传递到接收器配置方法,如下:
.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"
}
}
}
}
五、总结
- 本篇主要是对Serilog的说明,认识到是一个基于日志事件的而非日志消息的结构化日志类库。
- 简单的涉及对基础知识的认识以及使用,通过构建一个新的项目来实现Serilog的日志记录以及怎么使用这个框架。
- 在后续中如何结合这个日志类库引入项目中使用,以及对日志怎么存储和查询进行说明(会考虑 ELK存储采集分析 )。
- 如果有不对的或不理解的地方,希望大家可以多多指正,提出问题,一起讨论,不断学习,共同进步。
- 参考资料: 官方简介 、Serilog文档、serilog-aspnetcore
- 本文源码下载地址
基于.NetCore3.1系列 —— 日志记录之初识Serilog的更多相关文章
- 基于.NetCore3.1系列 —— 日志记录之日志配置揭秘
一.前言 在项目的开发维护阶段,有时候我们关注的问题不仅仅在于功能的实现,甚至需要关注系统发布上线后遇到的问题能否及时的查找并解决.所以我们需要有一个好的解决方案来及时的定位错误的根源并做出正确及时的 ...
- 基于.NetCore3.1系列 —— 日志记录之自定义日志组件
一.前言 回顾:日志记录之日志核心要素揭秘 在上一篇中,我们通过学习了解在.net core 中内置的日志记录中的几大核心要素,在日志工厂记录器(ILoggerFactory)中实现将日志记录提供器( ...
- 基于.NetCore3.1系列 —— 日志记录之日志核心要素揭秘
一.前言 在上一篇中,我们已经了解了内置系统的默认配置和自定义配置的方式,在学习了配置的基础上,我们进一步的对日志在程序中是如何使用的深入了解学习.所以在这一篇中,主要是对日志记录的核心机制进行学习说 ...
- 基于.NetCore3.1系列 ——认证授权方案之Swagger加锁
一.前言 在之前的使用Swagger做Api文档中,我们已经使用Swagger进行开发接口文档,以及更加方便的使用.这一转换,让更多的接口可以以通俗易懂的方式展现给开发人员.而在后续的内容中,为了对a ...
- 基于.NetCore3.1系列 —— 认证授权方案之授权揭秘 (下篇)
一.前言 回顾:基于.NetCore3.1系列 -- 认证授权方案之授权揭秘 (上篇) 在上一篇中,主要讲解了授权在配置方面的源码,从添加授权配置开始,我们引入了需要的授权配置选项,而不同的授权要求构 ...
- 记一次基于springboot+aop实现日志记录实战
1. 为什么要记录日志 好处: a. 可以对一些重要功能进行记录,方便以后跟踪是谁操作此功能的. b. 在操作某些功能时可能会发生异常,但每次出现异常我们想定位日志都要去服务器查看我们的日志.有了日志 ...
- .NET Core微服务之基于Exceptionless实现分布式日志记录
Tip: 此篇已加入.NET Core微服务基础系列文章索引 一.Exceptionless极简介绍 Exceptionless 是一个开源的实时的日志收集框架,它可以应用在基于 ASP.NET,AS ...
- 七、.net core(.NET 6)使用Serilog进行配置和实现日志记录
使用Serilog来实现日志记录 先安装Serilog六件套神装包: 也可以对个别相应的包进行删除等,都是可以的.例如,标注的1是读取配置文件的,如果不需要通过配置文件进行操作,就可以使用这个包.2是 ...
- 基于SqlSugar的开发框架循序渐进介绍(8)-- 在基类函数封装实现用户操作日志记录
在我们对数据进行重要修改调整的时候,往往需要跟踪记录好用户操作日志.一般来说,如对重要表记录的插入.修改.删除都需要记录下来,由于用户操作日志会带来一定的额外消耗,因此我们通过配置的方式来决定记录那些 ...
随机推荐
- PDO::exec
PDO::exec — 执行一条 SQL 语句,并返回受影响的行数(PHP 5 >= 5.1.0, PECL pdo >= 0.1.0) 说明 语法 int PDO::exec ( str ...
- luogu P3830 [SHOI2012]随机树 期望 dp
LINK:随机树 非常经典的期望dp. 考虑第一问:设f[i]表示前i个叶子节点的期望平均深度. 因为期望具有线性性 所以可以由每个叶子节点的期望平均深度得到总体的. \(f[i]=(f[i-1]\c ...
- linux的存储管理(RALD) LVM 逻辑卷管理 虚拟阵列
磁盘存储管理 LVM 逻辑卷 虚拟阵列 1.Linux系统中 磁盘使用 存在3个大问题: 1.灵活性 2.安全性 3.性能 2.解决办法RAID独立磁盘冗余阵列 RAID(Redundant Arra ...
- Python编程初学者指南PDF高清电子书免费下载|百度云盘
百度云盘:Python编程初学者指南PDF高清电子书免费下载 提取码:bftd 内容简介 Python是一种解释型.面向对象.动态数据类型的高级程序设计语言.Python可以用于很多的领域,从科学计算 ...
- ThreadLocal面试六连问
转自:码农沉思录 中高级阶段开发者出去面试,应该躲不开ThreadLocal相关问题,本文就常见问题做出一些解答,欢迎留言探讨. ThreadLocal为Java并发提供了一个新的思路, 它用来存储T ...
- 嵌入式linux简介
嵌入式linux系统应用非常广泛,涵盖各行各业,基于ARM.mips等微处理器架构的硬件平台.基于嵌入式linux系统的设备已经深入生活中各个角落,随处可见. 我们常说的嵌入式linux系统,其实 ...
- JS 留言板案例
css代码 ul { list-style: none; } ul li { background-color: pink; line-height: 40px; margin: 10px; widt ...
- java数组与数组异常
一 数组的定义 1.第一种定义方法: 格式: 数据类型[] 数组名=new 数据类型[数组长度] 2.第二种定义方法: 格式: 类型[] 数组名 = new 类型[]{元素,元素,.....} 3.第 ...
- JavaScript 手写setTimeout
let setTimeout = (sec, num) => { // 初始当前时间 const now = new Date().getTime() let flag = true let c ...
- 微软看上的Rust 语言,安全性真的很可靠吗
摘要:近几年,Rust语言以极快的增长速度获得了大量关注.其特点是在保证高安全性的同时,获得不输C/C++的性能.在Rust被很多项目使用以后,其实际安全性表现到底如何呢? 近几年,Rust语言以极快 ...