Serilog 源码解析——Sink 的实现

在上一篇中,我们简单地查看了 Serilog 的整体需求和大体结构。从这一篇开始,本文开始涉及 Serilog 内的相关实现,着重解决第一个问题,即 Serilog 向哪里写入日志数据的。(系列目录)

基础功能

在开始看 Serilog 怎么将日志记录到 Sinks 之前,先看下整体框架。首先,我们需要了解 Serilog 中最常用的一个接口ILogger,它提供了对外记录日志的所有功能 API 方法。

ILogger(核心接口)

在 Serilog 根目录下,保存有 4 个代码文件。类似于 LogDemo,ILogger内包含各种功能API方法,LogConfiguration用于构建对应的ILogger对象。另外,LogExtensions是向ILogger中添加新方法,不是LogConfiguration。

为了方便,我们首先看如何使用,在理解完使用方法,再回过头来看怎么创建。首先是ILogger, 它提供了大量的使用方法,按照功能主要分成以下三类。

方法名 说明
ForContext系列   构造子日志记录对象,并添加额外数据  
Write系列,XXX(日志等级名)系列   日志记录功能  
BindXXX 系列   输出模板、属性绑定相关  

这里面的方法,对我们而言,第二类方法是用的最多地,我们就先看 Serilog 是如何记录日志的吧。

Log(静态方法类)

这是一个静态类,可以看到内部本质上是对ILogger的进一步包装,并将所有API方法暴露出来,如下。

public static class Log { static ILogger _logger = SilentLogger.Instance; public static Logger { get => _logger; set => _logger = value ?? throw ... } public static void Write(LogEventLevel level, string messageTemplate) ... }

顺带提一句,类库中的SilentLogger类是对ILogger的一个空实现,它可以看成是一个具有调用功能的空类。

在了解到了最为核心的ILogger接口后,接下来需要了解的是描述日志事件的LogEvent类,该类在 Events 文件夹下,其作为Write的输入参数,可以将其想象成LogDemo中的LogData类,只不过它包含了更多的数据信息。另外,LogEventLevel是一个枚举,同样位于 Events 文件夹下,该类的内容和 LogDemo 中的LogLevel完全一致。

LogEvent(日志事件类)

在 Serilog 中,每当我们发生一次日志记录的行为时,Serilog 都将其封装到一个类中方便使用,即LogEvent类。和 LogDemo 中的LogData一样,LogEvent类包含一些描述日志事件的数据。

public class LogEvent { public DateTimeOffset Timestamp { get; } public LogEventLevel Level { get; } public Exception Exception { get; } public MessageTemplate MessageTemplate { get; } private readonly Dictionary<string, LogEventPropertyValue> _properties; internal LogEvent Copy() { ... } }

可以看到,在LogEvent中,有若干字段和属性描述一个日志事件。Timestamp属性描述日志记录的时间,采用DateTimeOffset这一类型可以统一不同时区下的服务器时间点,确保时间上的统一。Level就不用多说,描述日志的等级。Exception属性可以保存任意异常类数据,该属性常用在 Error 和 Fatal 等级中,需要保存异常信息时使用。至于后续的MessageTemplate和LogEventPropertyValue,从字面意义上看,属于字符串消息模板和记录数据时所用到,目前我们主力研究记录到 Sink 的处理逻辑,故这两块暂时不关心。

此外,在LogEvent类中,有一个很特别的函数,名为Copy函数,这个函数是根据当前LogEvent对象复制出了一个相同的LogEvent对象。这个方法可以看成是设计模式中原型模式的一种实现,只不过这个类没有利用IClonable接口来实现。

Core 目录下的功能类 ILogEventSink接口

在 LogDemo 中,我们通过ILogTarget接口定义不同的日志记录目的地。类似地,在 Serilog 中,所有的 Sink 通过ILogEventSink定义统一的日志记录接口。该接口如下所示。

public interface ILogEventSink { void Emit(LogEvent logEvent); }

该接口形式简单,只有一个函数,输入参数为LogEvent对象,无返回值,这一点和 LogDemo 中的ILogTarget接口很像。如果想实现一个 ConsoleSink,只需要将继承该接口并将LogEvent对象字符串数据写入到Console即可。实际上,在 Serilog.Sinks.Console 中其核心功能就是这么实现的。

Logger类

Logger类是对ILogger接口的默认实现。类似于 LogDemo 中的Logger,该类给所有日志记录的使用提供了 API 方法。考虑到本篇只关心日志向哪里写入的。因此,我们只关心其内部的部分字段属性和方法。

public sealed class Logger : ILogger, ILogEventSink, IDisposable { readonly ILogEventSink _sink; readonly Action _dispose; readonly LogEventLevel _minimumLevel; // 361行到375行 public void Write(LogEventLevel level, Exception exception, string messageTemplate, params object[] propertyValues) { if (!IsEnabled(level)) return; if (messageTemplate == null) return; if (propertyValues != null && propertyValues.GetType() != typeof(object[])) propertyValues = new object[] {propertyValues}; // 解析日志模板 _messageTemplateProcessor.Process(messageTemplate, propertyValues, out var parsedTemplate, out var boundProperties); // 构造日志事件对象 var logEvent = new LogEvent(DateTimeOffset.Now, level, exception, parsedTemplate, boundProperties); // 将日志事件分发出去 Dispatch(logEvent); } public void Dispatch(LogEvent logEvent) { ... // 将日志事件交给Sink进行记录 _sink.Emit(logEvent); } }

考虑到篇幅,这里我去掉了部分和当前功能无关的代码,只保留最为核心的代码。

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

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