Serilog源码解析——使用方法

在上两篇文章(链接1和链接2)中,我们通过一个简易 demo 了解到了一个简单的日志记录类库所需要的功能,即一条日志有哪些数据,以及如何通过一次记录的方式将同一条日志消息记录到多个日志媒介中。在本文中,针对 Serilog,我们从以下几个方面来了解 Serilog 核心功能需求和用法,并为下一篇正式开始探究源码准备相关工作。(系列目录)

Serilog 核心功能

目前,在 Asp.net core 中,对于日志记录库,除了微软官方准备的 Microsoft.Extensions.Logging 外,Serilog 也算是一个最常用的日志记录的类库。作为一类最常用的日志记录库,Serilog 具有良好的扩展性,其组织所维护的60多个项目均是为 Serilog 提供额外功能的扩展类库。为更好地了解源码具体在做什么,我们需要对 Serilog 有一个最基本的功能了解。

1. Serilog 将日志信息记录到哪里?

Serilog 将日志记录媒介称之为 Sink,在 Serilog 组织维护的类库中,有各种各样的 Sink,比如说写入控制台的 ConsoleSink 以及写入文件的 FileSink 等。这些 Sink 的包名通常命名为 Serilog.Sinks.XXX。

那么 Serilog 如何向多个 Sinks 中写入日志呢?下面给出一个向控制台和文件写入日志的例子。在此之前,我们需要向所在项目添加三个包:

Serilog

Serilog.Sinks.Console

Serilog.Sinks.File

添加完三个包之后,通过以下调用方式即可将日志写入到控制台和文件中。

var log = new LoggerConfiguration() .WriteTo.Console() .WriteTo.File("./log.txt") .CreateLogger(); log.Information("Hello world."); log.Error("Hello world, again.");

如果大家对之前的 Demo 了解的话,那么会觉得这段代码非常的熟悉。LoggerConfiguration类似于先前的LogBuilder,通过CreateLogger函数来创建对应的日志记录器,调用Console和File函数类似于先前的AddConsole以及AddFile方法,其输入参数的个数和形式都完全一样。但不同的是这些函数在WriteTo对象上调用而不是LoggerConfiguration上做调用,这一点和先前的 Demo 不一样,不过这一点并不影响我们的理解。再往后,日志的记录是通过Information和Error函数来调用,而LogDemo 中采用LogInformation和LogError函数记录日志。

Serilog输出到控制台上的日志信息

上图显示的是 Serilog 向控制台中记录的日志信息,可以看到,其最终记录的日志信息和 LogDemo 差不多,均是日志时间+等级+消息。

2. Serilog 向 Sink 中具体写入了什么?

在之前的 LogDemo 中,我们一直认为日志消息本质上就是一字符串。将日志记录下来就是将相关信息组合成字符串并写入到对应 Sink 中,这是非结构化日志记录库常用的做法。然而,这种使用方式有两个问题:

2.1 日志消息不能附带数据。日志消息附带数据有非常多的好处,比如说,如果类库具有自动解析数据的能力,那么我们只需要给出数据以及带有插入位置的消息字符串模板,就可以由类库自行构造对应的日志。在 Serilog 中,这种带有数据的日志消息被称为日志事件,它包含了待解析的字符串模板以及需要渲染的数据。

下面就取官网的例子做说明吧。可以看出,position是一个匿名类对象,它包含经度和纬度两个值。在使用日志记录的时候,向函数中传入三个参数,第一个是字符串模板,后两个是数据。其格式遵循Message Template定义,字符串模板的写法非常像C#中的$开头的字符串,字符串采用{}来标记数据的位置,采用:分割变量名和数据输出格式,二者的区别几乎只有开头有没有$。另外,在字符串模板中的变量名部分,还可以使用@和$来决定数据的渲染方法(即如何将数据内容写入到字符串中)。@采用的是解构方法,将内部内容取出直到基本类型后写入,$则是直接调用数据的ToString方法渲染。在下例中,position采用解构的方式渲染,而整数elapseMs利用000格式化字符串控制其渲染方法。

var position = new { Latitude = 25, Longitude = 134 }; var elapsedMs = 34; var log = new LoggerConfiguration() .WriteTo.Console() .CreateLogger(); log.Information("Processed {@Position} in {Elapsed:000} ms.", position, elapsedMs); // 输出: [20:54:34 INF] Processed {"Latitude": 25, "Longitude": 134} in 034 ms.

2.2 不同的人有不同的日志记录方式。举个例子,日志所包含的时间、等级和消息不同的人希望采用不同的格式输入。可以发现,在之前 Demo 中,通过LogData类给定的Tostring()方法转化不利于不同人的定制化需求。

对于这个需求,如果大家对前一个问题充分了解的话,可以发现,日志事件、日志等级以及日志消息都可以算是日志事件中的数据,我们可以通过设置输出模板(output template)达到。实际上,在 Serilog 中,大部分 Sink 都提供了一个默认的输出模板,通过提供自定义的输出模板可以达到日志信息定制化的目的。

var log = new LoggerConfiguration() .WriteTo.Console(outputTemplate: "({Timestamp:HH:mm:ss}/{Level}) {Message:lj}{NewLine}{Exception}") .CreateLogger(); log.Information("Hello world."); // 输出: (21:22:14/Information) Hello world.

这里通过设置outputTemplate输入参数来控制日志的输出格式。输出模板的改变会导致日志的输出内容发生变化,但可以看到其数据内容是一样的。

2.3 在完成前两个需求后,通过结合这二者,我们可以提供一个新的功能。即向日志中添加其他的自定义数据并将其渲染到日志中。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpfddg.html