上一篇中,我们提到了日志数据是如何进行解析了。然而,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;
}
}

整个流程并不复杂,所做的逻辑主要有以下几步。

  1. 因为该策略类主要负责字节数组的转换,因此需要将输入数据转换成字节数组,如果失败,表明该数据不能由当前策略成功转换,返回false
  2. 将字节数组转换成字符串,对于超出最大长度的部分仅记录其长度。此外,采用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类一样,该类对TypeMemberInfo两个类转换成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 源码解析——数据的保存(下)的更多相关文章

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

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

  2. Serilog 源码解析——数据的保存(中)

    上一篇文章中揭露了日志数据的绑定逻辑,主要说明了日志数据绑定的结果信息,即EventProperty结构体和LogEventProperty类,以及日志数据与具名属性Token的绑定类Property ...

  3. Serilog 源码解析——总览

    背景 大家好,考虑到在最近这些天,闲来无事,找了个类库好好研究下别人写的高质量代码,颇有收获,打算和大家分享下.考虑到最近在自学 ASP.NET Core 的相关开发,对 Serilog 这个日志记录 ...

  4. Serilog源码解析——使用方法

    在上两篇文章(链接1和链接2)中,我们通过一个简易 demo 了解到了一个简单的日志记录类库所需要的功能,即一条日志有哪些数据,以及如何通过一次记录的方式将同一条日志消息记录到多个日志媒介中.在本文中 ...

  5. Vue源码解析---数据的双向绑定

    本文主要抽离Vue源码中数据双向绑定的核心代码,解析Vue是如何实现数据的双向绑定 核心思想是ES5的Object.defineProperty()和发布-订阅模式 整体结构 改造Vue实例中的dat ...

  6. 渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(下)

    关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/08/12/es-code03/ 前提 上篇文章写完了 ES 流程启动的一部分,main 方法都入 ...

  7. Serilog 源码解析——解析字符串模板

    大家好啊,上一篇中我们谈到 Serilog 是如何决定日志记录的目的地的,那么从这篇开始,我们着重于 Serilog 是向 Sinks 中记录什么的,这个大功能比较复杂,我尝试再将其再拆分成几个小块方 ...

  8. Serilog 源码解析——Sink 的实现

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

  9. iOS富文本组件的实现—DTCoreText源码解析 数据篇

    本文转载 http://blog.cnbang.net/tech/2630/ DTCoreText是个开源的iOS富文本组件,它可以解析HTML与CSS最终用CoreText绘制出来,通常用于在一些需 ...

随机推荐

  1. python文件管道 下载图集

    # -*- coding: utf-8 -*- import re from time import sleep import scrapy from scrapy.linkextractors im ...

  2. C# 将DataTable里面的数据导出到excel

    //需要在bin里面添加 Interop.Microsoft.Office.Interop.Excel.dll 的引用 //添加引用 using System.Data; /// <summar ...

  3. sql优化整理(一)

    sql的编写语法是这样的: SELECT DISTINCT <select_list> FROM <left_table> <join_type> JOIN < ...

  4. shell中将带分隔符的字符串转为数组

    shell中将字符串列表转换成数组,需要将数组用括号来表示,元素用"空格"符号分割开,格式如下: array_name=(value1 ... valuen) 使用内置的分割符IF ...

  5. CSS动画菜鸡记录板

    Transition 过渡属性: (background 1s linear 0s) 缓动函数 linear,在 easings.net 可找到相应的功能 若想要多次不同执行,用逗号隔开 Animat ...

  6. pybind11和numpy进行交互

    使用一个遵循buffer protocol的对象就可以和numpy交互了.   这个buffer_protocol要有哪些东西呢? 要有如下接口: struct buffer_info { void ...

  7. linux 常用命令及零散知识

    磁盘管理常用命令 fdisk -l //展示磁盘使用情况 df -h      //展示目录挂载情况 du -sh   //查文件.文件夹的总大小 scp /home/a.txt /home/b.tx ...

  8. Java面试题集(一)答案汇总(1-22)

    java基础篇: 1.1.Java基础 (1)面向对象的特性:继承.封装和多态 以下都是查阅大神的博客后,摘录的内容:来源http://www.cnblogs.com/chenssy 1.继承 继承是 ...

  9. MongoDB简介---MongoDB基础用法(一)

    Mongo MongoDB是一个基于分布式文件存储的数据库.MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的. MongoDB 将数据存储为一 ...

  10. Mybatis初学经验----------------(2)

    至于myBatis的配置,上篇文章中有,就不说了.今天谈谈myBatis编写Dao层时的用法. 传统Dao层代码需求 1.在Dao层实现类中,存在大量的模板方法,能否提取模板方法,减少我们的工作量. ...