Serilog 源码解析——数据的保存(下)
上一篇中,我们提到了日志数据是如何进行解析了。然而,Serilog 灵活采用了不同的策略(Policy)决定一个日志对象如何解析到LogEventPropertyValue
的子类对象中,即采用了IScalarConversionPolicy
以及IDestructingPolicy
接口对数据做转换。在本篇中,着重强调这两个接口以及其实现类是如何做到这一功能的。(系列目录)
IScalarConversionPolicy
接口
interface IScalarConversionPolicy
{
bool TryConvertToScalar(object value, out ScalarValue result);
}
IScalarConversionPolicy
这个接口用来负责将日志数据转换成ScalarValue
的,这点从输入输出参数就可以看的出来,value
作为日志的输入数据,其类型为可接受任意数据对象的object
根类,而转换后的result
变量采用out
形式的输入参数,而参数的返回值为布尔类型,执行该转换成功与否。这种函数设计模式和 C# 中基础类型转换函数TryParse
类似,二者均将重要的数据以输入参数做传递,返回值仅指明当前处理方式是否成功。
ByteArrayScalarConversionPolicy
类
Serilog 中IScalarConversionPolicy
接口的实现类有3个,这里首先介绍第一个实现类:ByteArrayScalarConversionPolicy
。从名字上来看,大体就是字节数组转换到ScalarValue
这一功能。具体看下源码:
class ByteArrayScalarConversionPolicy : IScalarConversionPolicy
{
// 定义字节数组的最大长度
const int MaximumByteArrayLength = 1024;
public bool TryConvertToScalar(object value, out ScalarValue result)
{
var bytes = value as byte[];
if (bytes == null) // 转换失败
{
result = null;
return false;
}
if (bytes.Length > MaximumByteArrayLength)
{ // 长度超出限制
var start = string.Concat(bytes.Take(16).Select(b => b.ToString("X2")));
var description = start + "... (" + bytes.Length + " bytes)";
result = new ScalarValue(description);
}
else
{
result = new ScalarValue(string.Concat(bytes.Select(b => b.ToString("X2"))));
}
return true;
}
}
整个流程并不复杂,所做的逻辑主要有以下几步。
- 因为该策略类主要负责字节数组的转换,因此需要将输入数据转换成字节数组,如果失败,表明该数据不能由当前策略成功转换,返回
false
。 - 将字节数组转换成字符串,对于超出最大长度的部分仅记录其长度。此外,采用
X2
控制符转换,也就是按照16进制转换,每次转换成两个字符。
EnumScalarConversionPolicy
类
和上面的类相似,该类也是转换成ScalarValue
的一个策略类。从名字上来看,它的作用对象是枚举。
class EnumScalarConversionPolicy : IScalarConversionPolicy
{
public bool TryConvertToScalar(object value, out ScalarValue result)
{
if (value.GetType().GetTypeInfo().IsEnum)
{
result = new ScalarValue(value);
return true;
}
result = null;
return false;
}
}
相比之下,该类的处理逻辑更加的简单。只要判断是枚举数据,则直接用ScalarValue
类对象将其包裹起来。
SimpleScalarConversionPolicy
类
该类主要处理的是将认定为简单的数据类型进行包裹。
class SimpleScalarConversionPolicy : IScalarConversionPolicy
{
readonly HashSet<Type> _scalarTypes;
public SimpleScalarConversionPolicy(IEnumerable<Type> scalarTypes)
{
_scalarTypes = new HashSet<Type>(scalarTypes);
}
public bool TryConvertToScalar(object value, out ScalarValue result)
{
if (_scalarTypes.Contains(value.GetType()))
{
result = new ScalarValue(value);
return true;
}
result = null;
return false;
}
}
这个逻辑也很简单,只要被其内部哈希集合所包含的数据类型,均用ScalarValue
将其包裹。从构造函数里面可以看出,具体集合内部包含哪些数据类型则由外界传入。这里我们看下构造函数的调用地点,它在PropertyValueConverter
类中构造,这里重点关注下传入的参数BuiltInScalarTypes
,它是一个静态的数据结构,内部包含大部分的基础数据类型,即所有的数字类型、字符相关类型、时间相关类型等。换句话来说,如果传入的数据是这些数据,则直接用ScalarValue
进行包装。
IDestructuringPolicy
接口
除了IScalarConversationPolicy
这个将数据转化为ScalarValue
这个接口外,还有一个应用更加广泛的接口IDestructuringPolicy
,该接口所做的是将日志数据转化为LogEventPropertyValue
类型。(吐槽一句:实际上很多实现类还是将其变成ScalarValue
类,其功能和上述接口没有什么区别)
public interface IDestructuringPolicy
{
bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result);
}
该接口内的函数和上一个接口内函数类似,有输入的日志数据value
,转换后的result
数据,以及函数布尔返回值。除此之外,还有一个propertyValueFactory
,函数的注释对此描述为,递归采用策略来解构新的值,不是很明白它的意思,我们具体看实现类是如何操作的。
DelegateDestrcuturingPolicy
类
class DelegateDestructuringPolicy : IDestructuringPolicy
{
public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
{
if (value is Delegate del)
{
result = new ScalarValue(del.ToString());
return true;
}
result = null;
return false;
}
}
从源码上来看,该类是将委托转换成字符串的策略。虽然该类是对IDestructuringPolicy
接口的一个实现,然而其内部逻辑并没有涉及到propertyValueFactory
,且也是转换成ScalarValue
。从这个角度来看,该类更应该实现IScalarConversionPolicy
接口。
ReflectionTypesScalarDestructuringPolicy
类
和DelegateDestrcuturingPolicy
类一样,该类对Type
和MemberInfo
两个类转换成ScalarValue
。考虑篇幅,就略过,可以自行翻阅源码。
ProjectedDestructuringPolicy
类
class ProjectedDestructuringPolicy : IDestructuringPolicy
{
readonly Func<Type, bool> _canApply;
readonly Func<object, object> _projection;
public ProjectedDestructuringPolicy(Func<Type, bool> canApply, Func<object, object> projection)
{
_canApply = canApply ?? throw new ArgumentNullException(nameof(canApply));
_projection = projection ?? throw new ArgumentNullException(nameof(projection));
}
public bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result)
{
if (value == null) throw new ArgumentNullException(nameof(value));
if (!_canApply(value.GetType()))
{
result = null;
return false;
}
var projected = _projection(value);
result = propertyValueFactory.CreatePropertyValue(projected, destructureObjects: true);
return true;
}
}
ProjectedDestructuringPolicy
类它是将一个日志数据对象投影到另一个数据对象然后再进行解析。其内部包含两个泛型委托,其数据均要求从构造函数内给出,分别为:
_canApply
委托,表明当前日志数据的数据类型是否适用于转换规则,输入为Type
数据,即日志数据的数据类型,返回值为bool
类型,表明为是否能够投影到新数据上_projection
委托,表明当前日志数据如何投影到另一种数据类型中。换句话来说,它定义了转换规则。
之后,在转换时,首先判断是否可以投影,如果不能则直接返回。如果可以,则先投影到新数据对象上,在利用输入参数给出的propertyValueFactory
对象对其进行转换。这里明确的是,它不负责投影后数据的具体转换操作,反而由输入参数进行转换。
实际上,在大多数的调用方式中,
propertyValueFactory
这一参数均使用的是_depthLimiter
这个对象,也就是DepthLimiter
类对象。也就是说,投影后的数据最终又交回给PropertyValueConveter
这个类对象处理,通过这种递归的方式,一层层进行处理。
总结
到目前为止,日志记录的整个解析过程就已经结束了。纵观这几篇文章,其大体思路是,将日志记录时的字符串模板进行解析,拆分成若干个 Token 数据。随后,将后续的日志数据封装到对应的LogEventProperty
中。最后,加上整个日志所需要的其他基础信息,比如说日志时间、日志等级等,最终构成了LogEvent
对象。按照流程,在LogEvent
对象构建完毕后,该对象交给对应的 Sink 渲染成对应的数据,在下一篇中,我们将着重讲述 Serilog 中通用的渲染方法。
Serilog 源码解析——数据的保存(下)的更多相关文章
- Serilog 源码解析——数据的保存(上)
在上一篇中,我们主要研究了Serilog是如何解析字符串模板的,它只是单独对字符串模板的处理,对于日志记录时所附带的数据没有做任何的操作.在本篇中,我们着重研究日志数据的存储方式.(系列目录) 本篇所 ...
- Serilog 源码解析——数据的保存(中)
上一篇文章中揭露了日志数据的绑定逻辑,主要说明了日志数据绑定的结果信息,即EventProperty结构体和LogEventProperty类,以及日志数据与具名属性Token的绑定类Property ...
- Serilog 源码解析——总览
背景 大家好,考虑到在最近这些天,闲来无事,找了个类库好好研究下别人写的高质量代码,颇有收获,打算和大家分享下.考虑到最近在自学 ASP.NET Core 的相关开发,对 Serilog 这个日志记录 ...
- Serilog源码解析——使用方法
在上两篇文章(链接1和链接2)中,我们通过一个简易 demo 了解到了一个简单的日志记录类库所需要的功能,即一条日志有哪些数据,以及如何通过一次记录的方式将同一条日志消息记录到多个日志媒介中.在本文中 ...
- Vue源码解析---数据的双向绑定
本文主要抽离Vue源码中数据双向绑定的核心代码,解析Vue是如何实现数据的双向绑定 核心思想是ES5的Object.defineProperty()和发布-订阅模式 整体结构 改造Vue实例中的dat ...
- 渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(下)
关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/08/12/es-code03/ 前提 上篇文章写完了 ES 流程启动的一部分,main 方法都入 ...
- Serilog 源码解析——解析字符串模板
大家好啊,上一篇中我们谈到 Serilog 是如何决定日志记录的目的地的,那么从这篇开始,我们着重于 Serilog 是向 Sinks 中记录什么的,这个大功能比较复杂,我尝试再将其再拆分成几个小块方 ...
- Serilog 源码解析——Sink 的实现
在上一篇中,我们简单地查看了 Serilog 的整体需求和大体结构.从这一篇开始,本文开始涉及 Serilog 内的相关实现,着重解决第一个问题,即 Serilog 向哪里写入日志数据的.(系列目录) ...
- iOS富文本组件的实现—DTCoreText源码解析 数据篇
本文转载 http://blog.cnbang.net/tech/2630/ DTCoreText是个开源的iOS富文本组件,它可以解析HTML与CSS最终用CoreText绘制出来,通常用于在一些需 ...
随机推荐
- centos8安装fastdfs6.06(单机方式)
一,下载 fastdfs6.06 1,官方地址 https://github.com/happyfish100 2,说明:当前版本:共3个子模块 fastdfs v6.06 libfastcommon ...
- Redis Lua脚本完全入门
1. 前言 Redis是高性能的KV内存数据库,除了做缓存中间件的基本作用外还有很多用途,比如胖哥以前分享的Redis GEO地理位置信息计算.Redis提供了丰富的命令来供我们使用以实现一些计算.R ...
- influxdb集群部署
环境准备 influxdb enterprise运行条件最低需要三个meta nodes节点以及两个data nodes Meta nodes之间使用TCP和Raft一致性协议通信,默认端口为8089 ...
- 使用creata-react-app脚手架创建react项目时非常慢的问题
创建react项目必须要有下面两个步骤 cnpm install -g create-react-app //创建react全局变量 create-react-app my-app //创建一个re ...
- ERROR [RMI TCP Connection(3)-127.0.0.1] - init datasource error
运行报错 ERROR [RMI TCP Connection(3)-127.0.0.1] - init datasource error, url: jdbc:mysql://localhost:33 ...
- 缩略图调查——抖音客户端/PC/iphone
最近对抖音有点上瘾,经常看到这样的视频列表: 由于抖音平台的限制,用户最多只能上传60s的视频,因此分段为3个视频.而在视频列表的缩略图模式下,三个视频的封面恰好组合成一张图像.这种方式比较符合审美标 ...
- linux mount 挂载提示 mount: you must specify the filesystem type
解决方法: mkfs.ext3 /dev/vdv mount -t ext3 /dev/vdv /usr1
- ThreadLocal使用说明
让变量只能在这个线程内被读写,在其他线程内无法被访问.以键值对存放变量,并继承弱应用,内存随时会被回收,用完要remove不然会内存泄漏,使用的时候直接设置值就可以了,键就是ThreadLocal本身 ...
- LuoguP1286 两数之和
题面概括 将n个数两两相加得到n*(n-1)/2个和,给出这些和,求所有原数方案 n<=500 LuoguP1286 题解 此题原题是 n<10, 没啥可做的 先将 \(n*(n-1)/2 ...
- CodeForces 1408I Bitwise Magic
题意 给定三个整数 \(n,k,c\) 和一个长度为 \(n\) 的序列 \(a\),保证 \(a_i\) 互不相同.可以操作 \(k\) 次,每次随机选择一个 \(a_i\) 变成 \(a_i-1\ ...