Serilog 源码解析——数据的保存(上)

在上一篇中,我们主要研究了Serilog是如何解析字符串模板的,它只是单独对字符串模板的处理,对于日志记录时所附带的数据没有做任何的操作。在本篇中,我们着重研究日志数据的存储方式。(系列目录)

本篇所解决的内容

本文主要讲述在Serilog中日志记录器是如何记录数据的,即在上一篇文章中解析部分的第二件事。和之前的文章架构一样,本篇文章主要从数据存储和行为逻辑两个方面做阐述。

public void Process(string messageTemplate, object[] messageTemplateParameters, out MessageTemplate parsedTemplate, out EventProperty[] properties) { parsedTemplate = _parser.Parse(messageTemplate); // 第一件事 properties = _propertyBinder.ConstructProperties(parsedTemplate, messageTemplateParameters); // 第二件事 }

考虑到数据保存的逻辑比较复杂,涉及到的类结构比较多,计划将该部分逻辑拆成两个部分,方便理解。

EventProperty结构体

首先看下数据存储所使用到的数据类。ConstructProperties方法返回的是EventProperty结构体数组。数组比较好理解,一个数据对应一个EventProperty结构。EventProerty结构从字面意思上可以看出来,下面是EventProperty核心部分。

readonly struct EventProperty { public string Name { get; } public LogEventPropertyValue Value { get; } }

这个结构体非常的简单,内部只记录该属性的名称和对应的数据,Name好理解,它是该数据的名称,为字符串类型。另一个则是LogEventPropertyValue对象,它保存了对应数据。另外,该类被readonly所修饰,表明该类是一个只读的结构体,一旦被创建出来,就无法修改内部的数据。

LogEventProperty类

在 Serilog 中,有一个和EventProperty结构体功能差不多的类,即LogEventProperty类。从下面的代码可以看出,二者没有太大的差别。和上面的结构一样,这两个代码文件均位于 Event 文件夹中,都是和数据相关的。

public class LogEventProperty { public string Name { get; } public LogEventPropertyValue Value { get; } } LogEventPropertyValue类及其继承类

在上一节,我们认为LogEventPropertyValue是保存相关数据的。在说明这个类之前,不知道有没有人会很好奇一点,为什么会有LogEventPropertyValue这个类?按道理,保存数据对象没必要那么大费周章,只需要用object类即可,毕竟object类是万物所有类的基类,没有任何必要额外构建新类。那么,在 Serilog 中,为什么要使用LogEventPropertyValue来保存数据呢?我们先看下这个类有什么。

public abstract class LogEventPropertyValue : IFormattable { public abstract void Render(TextWriter output, string format = null, IFormatProvider formatProvider = null); public string ToString() => ToString(null, null); public string ToString(string format, IFormatProvider formatProvider) { var output = new StringWriter(); Render(output, format, formatProvider); return output.Tostring(); } }

可以看到,LogEventPropertyValue类是一个抽象类,它继承于IFormattable接口,从其内部的函数可以看出,似乎都是和渲染相关,看不出来和数据保存有什么关系。是我们弄错了么?LogEventPropertyValue根本不是保存数据用的?

这里我自己有一个回答,不一定保证正确。首先,回到上一个问题,为什么不采用object而是使用新类。实际上,如果只从记录数据的角度来看,object类足够用了。然而,使用object类型有一个非常麻烦的问题,那就是不同的数据类型有不同的渲染方式,对于一个object类型的数据如何进行渲染是一个很麻烦的操作。对于原始数据类型,我们只需要调用其ToString方法将其转换成字符串,数组则将数据渲染到[]中,字典则是将数据渲染到{}中,而更加复杂的数据类型类型,考虑其渲染形式,可能利用其ToString方法渲染($操作符),也有可能解构该对象渲染(@操作符),具体渲染形式由字符串模板内给出。对于这样一个复杂的渲染逻辑,如果只使用object对象,那么在渲染阶段会构造一段非常复杂且难以维护的if-else语句块。

public string Render(object obj) { if (obj.GetType() == typeof(int) || obj.GetType() == typeof(double) || ...) { return obj.ToString(); } else if (obj.GetGenericTypeDefinition() == typeof(IEnumerable<>)) { ... } }

更好的办法,就是将不同的渲染策略封装到对应的类中,即通过策略模式在不同的继承类中重写对应的渲染逻辑。在 Serilog 中所展现出来的就是,以LogEventPropertyValue为根类,若干不同渲染方法的继承类ScalarValue、SquenceValue、DictionaryValue、StructureValue。明白了这点后,就可以明白LogEventPropertyValue所提供的函数了,其抽象函数Render就表示子类需要重写的渲染逻辑。Serilog 将数据的渲染逻辑分成四大类:

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

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