Serilog 源码解析——数据的保存(中)
上一篇文章中揭露了日志数据的绑定逻辑,主要说明了日志数据绑定的结果信息,即EventProperty结构体和LogEventProperty类,以及日志数据与具名属性Token的绑定类PropertyBinder。在本文中,我们主要对PropertyValueConverter类及其涉及的其他相关类进行说明。关注的重点在如何利用具名属性 Token 以及对应的日志数据来构造对应的LogEventPropertyValue类对象。(系列目录)
PropertyValueConverter类
PropertyValueConverter类是一个非常复杂的类。Serilog将其分成两个代码文件存储,分别为./Capturing/PropertyValueConverter.cs以及./Capturing/DepthLimiter.cs文件。先看下PropertyValueConverter有什么字段和属性。
partial class PropertyValueConverter : ILogEventPropertyFactory, ILogEventPropertyValueFactory
{
readonly IDestructuringPolicy[] _destructuringPolicies;
readonly IScalarConversionPolicy[] _scalarConversionPolicies;
readonly DepthLimiter _depthLimiter;
readonly int _maximumStringLength;
readonly int _maximumCollectionCount;
readonly bool _propagateExceptions;
...
}
好家伙,一次性来了一大堆第一次见的玩意。一个个看,一个个猜,先弄懂大意再说。
ILogEventPropertyFactory和ILogEventPropertyValueFactory接口
首先就是ILogEventPropertyFactory和ILogEventPropertyValueFactory接口,从Factory名称大概猜出来,属于工厂模式的一种设计,是构造对应的LogEventProperty和LogEventPropertyValue对象。这些接口位于./Core文件夹内部,表明是非常重要的接口。
public interface ILogEventPropertyValueFactory
{
LogEventPropertyValue CreatePropertyValue(object value, bool destructureObjects = false);
}
public interface ILogEventPropertyFactory
{
LogEventProperty CreateProperty(string name, object value, bool destructureObjects = false);
}
从函数签名上看,基本和猜测一致,用于构建对应的对象,最后一个输入参数指明是否以解构对象的方式进行构建。
IDestructuringPolicy和IScalarConversionPolicy接口
在PropertyValueConverter类中,还保有对IDestructuringPolicy和IScalarConversionPolicy接口数组的引用。字面意义上,这两个接口均描述的是一个策略,且保存在./Core文件夹下。这两个接口有什么用,做什么,先看下代码吧。
public interface IDestructuringPolicy
{
bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result);
}
public interface IScalarConversionPolicy
{
bool TryConvertToScalar(object value, out ScalarValue result);
}
从这两个接口内的函数签名可以猜测出它们分别将日志数据转化成对应的数据类型。bool返回值标注该转换是否成功,输入参数中被 out 修饰的变量则可以看成是转换成功后的结果变量。以此,可以发现,IDestrucuringPolicy接口用于将数据转换成LogEventPropertyValue对象,而IScalarConversionPolicy接口则将数据转换成ScalarValue。
DepthLimiter类
DepthLimiter类对象是ProertyValueConverter所持有的最后一个复杂的类对象。有意思的是,该类的作用范围放在PropertyValueConverter类的内部。换句话来说,就是DepthLimiter是PropertyValueConveter的内部类。
class DepthLimiter : ILogEventPropertyValueFactory
{
[ThreadStatic]
static int _currentDepth;
readonly int _maximumDestructingDepth;
readonly PropertyValueConverter _propertyValueConverter;
public static void SetCurrentDepth(int depth)
{
_currentDepth = depth;
}
public LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructuring)
{
var storedDepth = _currentDepth;
var result = DefaultIfMaximumDepth(storedDepth) ??
_propertyValueConverter.CreatePropertyValue(value, destructuring, storedDepth + 1);
_currentDepth = storedDepth;
return result;
}
...
}
可以看到,DepthLimiter也是一个处理日志消息数据与LogEventPropertyValue相绑定的过程类。和PropertyValueConverter类一样,它也继承于ILogEventPropertyValueFactory接口。然而,DepthLimiter和PropertyValueConverter不同之处在于:
DepthLimiter只负责对LogEventPropertyValue的创建,而PropertyValueConveter不仅负责前者的构建,还负责对LogEventProperty的创建,这一点从所继承的接口数目可以看出来。也就是说,PropertyValueConverter的作用范围比DepthLimiter大。DepthLimiter类不负责具体的构建,这一点可以从其包含_propertyValueConverter字段可以看出来,具体对日志数据的渲染还是交给内部的PropertyValueConverter类对象来处理。那么DepthLimiter负责什么呢?它负责记录处理的深度,这一点从_currentDepth这个变量可以看出来。
考虑这样一个数据:
class A
{
public B B { get; }
}
class B
{
public int C { get; }
}
数据A具有非常复杂的形式,A 中有 B 的属性,B 内有int类型的C属性,一共三层。最外层为 A,最内层为 C。如果采用解构的方式记录数据 A 的对象,那么需要深入 3 层迭代转换。比如说C数据应该放在ScalarValue中,B和A应该放在StructValue中。Serilog通过PropertyValueConverter和DepthLimiter的相互引用配合达到递归转换的目的。也就是说,DepthLimiter负责维护递归转换时当前转换数据所处的深度。
剩余参数
在PropertyValueConverter中,还剩下一些较为简单的数据参数。在了解了DepthLimiter之后,剩余的三个参数可以很明显看出来。
_maximumStringLength:指的是构造日志信息字符串的最大长度_maximumCollectionCount: 指的是日志数据集合解析的最大个数_propgateExceptions: 该值是一个bool类型,表示在保存日志数据的过程中,若发生了异常,则相关异常是否被抛出。
接口函数
接下来,我们把注意力再回到PropertyValueConverter类中对IDestructuringPolicy和IScalarConversionPolicy接口函数实现的部分。
public LogEventProperty CreateProperty(string name, object value, bool destructureObjects = false)
{
return new LogEventProperty(name, CreatePropertyValue(value, destructureObjects));
}
public LogEventPropertyValue CreatePropertyValue(object value, bool destructureObjects = false)
{
return CreatePropertyValue(value, destructureObjects, 1);
}
可以看到,无论是哪种接口,其内部直接或间接在调用CreatePropertyValue(object value, Destructuring destructuring, int depth)函数(类文件中第126行)。下面是该函数的核心部分代码。
LogEventPropertyValue CreatePropertyValue(object value, Destructuring destructuring, int depth)
{
if (value == null)
return new ScalarValue(null);
...
// ScalarValue的转换策略
foreach (var scalarConversionPolicy in _scalarConversionPolicies)
{
if (scalarConversionPolicy.TryConvertToScalar(value, out var converted))
return converted;
}
// 设置深度
DepthLimiter.SetCurrentDepth(depth);
// 如果Token采用解构渲染,则使用解构策略尝试解析数据
if (destructuring == Destructuring.Destructure)
{
foreach (var destructuringPolicy in _destructuringPolicies)
{
if (destructuringPolicy.TryDestructure(value, _depthLimiter, out var result))
return result;
}
}
// 获取日志数据的数据类型,利用内置的解析策略对其解析
var valueType = value.GetType();
if (TryConvertEnumerable(value, destructuring, valueType, out var enumerableResult))
return enumerableResult;
if (TryConvertValueTuple(value, destructuring, valueType, out var tupleResult))
return tupleResult;
if (TryConvertCompilerGeneratedType(value, destructuring, valueType, out var compilerGeneratedResult))
return compilerGeneratedResult;
// 如果以上策略都不满足的话,直接构造
return new ScalarValue(value.ToString());
}
这个函数非常复杂,但大体上分成以下几个步骤。
- 如果传入的数据是一个
null对象,则直接使用ScalarValue构造。 - 利用内部保存的
IScalarConversionPolicy数组尝试对日志数据绑定,如果能绑定,则将结果返回,否则继续。 - 设置深度值,之所以将深度值放在这里设置,是因为后续操作可能会用到这个深度值。
- 利用内部维护的
IDestructingPolicy数组尝试对日志数据绑定,如果能绑定,则返回结果值,否则继续。 - 获取当前日志数据的类型,并采用3个内置的绑定规则进行绑定。如果能成功,则返回结果值,如果所有都无法成功,则认为该对象无法在现有的规则下绑定,则采用
ScalarValue对其绑定。
第一步好理解,如果值为空,则直接采用ScalarValue对其渲染,ScalarValue会将其渲染成null。之所以不返回null对象,则是尝试对null操作调用函数等操作会抛出异常,而如果在调用前编写判断的逻辑会大大干扰程序员编写代码的逻辑。因此,直接使用new ScalarValue(null)会更加方便,不易出错。
第二步则是利用多个IScalarConversionPolicy策略类将传入的日志对象数据尝试转换成ScalarValue对象,一旦某个策略能够成功转换,那么直接跳出。
第三步和第四步的作用是尝试利用IDestructuringPolicy策略类对输入的日志数据进行转换。该部分将日志数据按照策略的要求尝试转换成LogEventPropertyValue类对象,和之前一样,一旦某个策略成功,则直接跳出。Serilog中定义了一组相关策略,其类代码保存在./Policies文件夹中。
最后,如果以上所有策略都没法满足时,则尝试采用内置的策略。
总结
总的来说,当记录日志时,其所附带的日志数据通过PropertyValueConverter类对象将其转化成一系列的LogEventPropertyValue对象,最终变成LogEventProperty对象。这些对象有着不同的语义,在渲染的行为方式上表现有所不同,比如说ScalarValue表示的一个原始数据,它采用ToString的方法对数据进行渲染;SequenceValue表示一类数组,采用[]方式对其渲染;DictionaryValue表示一组键值对,采用{}方式对其渲染;StructureValue表示一个复杂且需要解构的日志数据对象,采用{}方式渲染。除此之外,PropertyValueConverter还包含一个内部类DepthLimiter,该类是对PropertyValueConverter类的进一步装饰,让数据的解析添加了深度信息。在下一篇中,我们进一步来看IScalarConversionPolicy、IDestructuringPolicy以及它们的实现类。
Serilog 源码解析——数据的保存(中)的更多相关文章
- Serilog 源码解析——数据的保存(上)
在上一篇中,我们主要研究了Serilog是如何解析字符串模板的,它只是单独对字符串模板的处理,对于日志记录时所附带的数据没有做任何的操作.在本篇中,我们着重研究日志数据的存储方式.(系列目录) 本篇所 ...
- Serilog 源码解析——数据的保存(下)
上一篇中,我们提到了日志数据是如何进行解析了.然而,Serilog 灵活采用了不同的策略(Policy)决定一个日志对象如何解析到LogEventPropertyValue的子类对象中,即采用了ISc ...
- Serilog 源码解析——总览
背景 大家好,考虑到在最近这些天,闲来无事,找了个类库好好研究下别人写的高质量代码,颇有收获,打算和大家分享下.考虑到最近在自学 ASP.NET Core 的相关开发,对 Serilog 这个日志记录 ...
- Serilog源码解析——使用方法
在上两篇文章(链接1和链接2)中,我们通过一个简易 demo 了解到了一个简单的日志记录类库所需要的功能,即一条日志有哪些数据,以及如何通过一次记录的方式将同一条日志消息记录到多个日志媒介中.在本文中 ...
- Vue源码解析---数据的双向绑定
本文主要抽离Vue源码中数据双向绑定的核心代码,解析Vue是如何实现数据的双向绑定 核心思想是ES5的Object.defineProperty()和发布-订阅模式 整体结构 改造Vue实例中的dat ...
- Serilog 源码解析——解析字符串模板
大家好啊,上一篇中我们谈到 Serilog 是如何决定日志记录的目的地的,那么从这篇开始,我们着重于 Serilog 是向 Sinks 中记录什么的,这个大功能比较复杂,我尝试再将其再拆分成几个小块方 ...
- iOS富文本组件的实现—DTCoreText源码解析 数据篇
本文转载 http://blog.cnbang.net/tech/2630/ DTCoreText是个开源的iOS富文本组件,它可以解析HTML与CSS最终用CoreText绘制出来,通常用于在一些需 ...
- Serilog 源码解析——Sink 的实现
在上一篇中,我们简单地查看了 Serilog 的整体需求和大体结构.从这一篇开始,本文开始涉及 Serilog 内的相关实现,着重解决第一个问题,即 Serilog 向哪里写入日志数据的.(系列目录) ...
- JavaScipt 源码解析 数据缓存
常见的内存泄露的几种情况: 循环引用 JavaScript闭包 DOM插入 一个DOM对象被一个JavaScript对象引用,同时又引用同一个或其他的JavaScript对象,这个DOM对象可能回引发 ...
随机推荐
- golang通过cgo调用lua
目录 1.前期准备 2.测试go代码 3.完成的一个学习项目 4.总结 1.前期准备 1.第三方库:https://github.com/aarzilli/golua 2.下载lua源码:https: ...
- C语言/C++编程学习:送给考计算机二级的同学:公共基础知识总结!
数据结构与算法 1.算法 算法:是指解题方案的准确而完整的描述. 算法不等于程序,也不等计算机方法,程序的编制不可能优于算法的设计. 算法的基本特征:是一组严谨地定义运算顺序的规则,每一个规则都是有效 ...
- 干货分享:用一百行代码做一个C/C++表白小程序,程序员的浪漫!
前言:很多时候,当别人听到你是程序员的时候.第一印象就是,格子衫.不浪漫.直男.但是程序员一旦浪漫起来,真的没其他人什么事了.什么纪念日,生日,情人节,礼物怎么送? 做一个浪漫的程序给她,放上你们照片 ...
- CSDN的Markdown编辑器实用技巧(傻瓜式教程)
markdown编辑器被很多人声称是可以取代word的文字编辑器,其优点我们在这就不再过多赘述了,但对于一些初次接触的人来说,或多或少都有还些不适应,其主要原因在于一些常见的功能突然不知道怎么实现,所 ...
- c#封装ActiveX接口实践分析
ActiveX接口 是一个开放的集成平台,为开发人员.用户和 Web生产商提供了一个快速而简便的在 Internet 和 Intranet 创建程序集成和内容的方法. 使用 ActiveX, 可轻松方 ...
- OpenSpiel 随笔 05.14
------------恢复内容开始------------ 这两天年总算把自己的游戏写完了,也通过了所有的测试. 我将自己的代码上传到了我的github上, 地址是 https://github.c ...
- 详解MapReduce(Spark和MapReduce对比铺垫篇)
本来笔者是不打算写MapReduce的,但是考虑到目前很多公司还都在用这个计算引擎,以及后续要讲的Hive原生支持的计算引擎也是MapReduce,并且为Spark和MapReduce的对比做铺垫,笔 ...
- spring mvc 中获取HttpServletRequest ,HttpServletResponse
spring中的bean最常用的 singleton 模式 如果要在springmvc Controller 中获取 HttpServletRequest ,HttpServletResponse ...
- CSS动画之动画模块
过渡模块和动画模块的区别:过渡动画需要人为的去触发这个反应,而动画模块不需要人为相同点:都是动画:都是系统新增的属性:都是要满足三要素才有动画效果三要素:1.告诉系统执行哪个动画,要写出动画名称:an ...
- python机器学习实现线性回归
线性回归 关注公众号"轻松学编程"了解更多. [关键词]最小二乘法,线性 一.普通线性回归 1.原理 分类的目标变量是标称型数据,而回归将会对连续型的数据做出预测. 应当怎样从一大 ...