Serilog 源码解析——解析字符串模板
大家好啊,上一篇中我们谈到 Serilog 是如何决定日志记录的目的地的,那么从这篇开始,我们着重于 Serilog 是向 Sinks 中记录什么的,这个大功能比较复杂,我尝试再将其再拆分成几个小块方便大家理解。(系列目录)
本篇要解决什么
之前提到,在Logger类中构造对应的LogEvent对象之前,日志记录器通过MessageTemplateProcessor类对象的Process方法处理字符串模板和传入进来的数据信息。这个方法内部只是做了两件事:
- 解析消息模板,分析哪些是字符串字面值哪些是需要转换的属性值
- 构造相关的数据对象
public void Process(string messageTemplate, object[] messageTemplateParameters, out MessageTemplate parsedTemplate, out EventProperty[] properties)
{
parsedTemplate = _parser.Parse(messageTemplate); // 第一件事
properties = _propertyBinder.ConstructProperties(parsedTemplate, messageTemplateParameters); // 第二件事
}
这篇文章主要分析第一件事的处理方法。之后将对应的数据与模板信息绑定内容则放在下一篇中。
MessageTemplate类
在分析如何处理之前,需要弄明白这个功能函数的输入是什么,输出是什么,在对生成什么东西有一定了解后,才能更加方便了解其运行机理。这里,在第一行代码可以发现,输入是一个字符串,而输出则是一个MessageTemplate类对象。因此,有必要对MessageTemplate类深入研究。MessageTemplate类保存在 Core 文件夹下,和LogEvent类一样,都是保存数据而用。这也就说明,MessageTemplate也是LogEvent中的一个属性,表明它是日志事件数据中的一部分。
MessageTemplate类中有很多的属性和方法,这里仅考虑一些较为重要的属性。
public class MessageTemplate
{
public string Text { get; }
readonly MessageTemplateToken[] _tokens;
internal ProertyToken[] NamedProperties { get; }
internal ProertyToken[] PositionalProerties { get; }
...
}
Text属性不用多说,该值为传入的字符串模板数据。接下来是MessageTemplateToken对象,该对象描述的是模板解析的结果,主要包含两类 Token,一个是文本 Token,即TextToken类,它描述的是模板中的文本信息,另一个是属性 Token,即PropertyToken类,描述的是模板内需要替换的属性数据名。这些类均是描述解析后的结果信息,且类文件均位于在 Parsing 文件夹中,且都继承于MessageTemplateToken类。在MessageTemplate类中,通过引用MessageTemplateToken数组来达到保有模板解析的结果信息。从变量名上可以发现,MessageTemplate类对象内所拥有的NameProperties和PositionProperties均描述一组属性 Token,二者的区别在于:前者描述的是具名的属性Token,该Token在字符串中具有具体的名字;后者描述的是位置的属性Token,即它在字符串模板中以位置数据出现。
举个例子,如果字符串模板为版本{version},那么其中版本就是文本 Token,version是具名属性 Token;如果字符串模板为版本{0},那么0则是位置的属性Token,它表示使用后续第一个值作为它的数据。
MessageTemplateToken类及其继承类
前面提到了 Token 这一描述结果的类型,接下来就是看描述这些 Token 是如何实现自己的功能的。
作为描述字符串解析结果的基类MessageTemplateToken,它主要包含两大属性,StartIndex描述该Token在字符串模板中的起始位置,Length描述该Token的长度。另外,这个类是一个抽象类,不允许直接实例化该类。
public abstract class MessageTemplateToken
{
public int StartIndex { get; }
public abstract int Length { get; }
}
接下来是文本 Token,即TextToken类。这个类非常简单,既然文本 Token 只描述模板中的文本部分,它只需要包含描述文本的Text属性,其长度也就被设置为文本的长度。
public sealed class TextToken : MessageTemplateToken
{
public string Text { get; }
public override int Length => Text.Length;
}
之后是属性 Token,即PropertyToken类。
public sealed class PropertyToken : MessageTemplateToken
{
readonly string _rawText;
readonly int? _position;
public override int Length => _rawText.Length;
public string PropertyName { get; }
public Destructuring Destructuring { get; }
public string Format { get; }
public Alignment? Alignment { get; }
public bool IsPositional => _position.HasValue;
}
从上面的代码可以看出来,该类要比TextToken复杂。这里一个个来分析:_rawText变量顾名思义,表示字符串模板中属性字符串,通常为花括号所括起来的部分。position作为一个可空int型数据,描述该属性Token的位置,这里只有位置的属性Token才有该值,具名的属性Token该值为空,二者的从IsPositional属性来区分。Length表示原始字符串的长度。PropertyName属性记录的是属性 Token 的名字。而Destructuring属性指明该属性值应该如何渲染(模板中的变量采用$还是@渲染,即采用数据本身类的ToString方法还是将数据对象解构再渲染),Format指明输出的格式化字符串,Alignment属性指明对其的方式,默认左对齐,通过设置可以让日志右对齐。举个例子,比如字符串模板为{version: 000},那么其_rawText值为{version: 000}, _position为null, Length为14,PropertyName为version,Destructuring值为Default,Format值为000,Alignment为默认值null,IsPositional为false。
总的来说,MessageTemplate类描述字符串模板解析后的数据,自然也是LogEvent类中的一个重要属性。在MessageTemplate中,维护一组经解析后的MessageTemplateToken数组,不同的 Token 用不同的类来描述,即描述文本信息的TextToken以及描述属性信息的PropertyToken。
MessageTemplateCache类
在了解完数据的存储部分后,接下来需要弄清楚的就是处理生成这些数据类的行为类。在MessageTemplateProcessor类的Process函数中,负责处理字符串模板解析的是_parser字段,它属于MessageTemplateCache类。那么首先看下其内部的结构。
interface IMessageTemplateParser
{
MessageTemplate Parse(string messageTemplate);
}
class MessageTemplateCache : IMessageTemplateParser
{
readonly IMessageTemplateParser _innerParser;
readonly object _templatesLock = new object();
readonly HashTable _templates = new HashTable();
public MessageTemplateCache(IMessageTemplateParser innerParser)
{
_innerParser = innerParser;
}
public MessageTemplate Parse(string messageTemplate)
{
...
// 第一步
var result = (MessageTemplate)_templates[messageTemplate];
if (result != null) return result;
// 第二步
result = _innerParser.Parse(messageTemplate);
// 第三步
lock (_templatesLock)
{
...
_templates[messageTemplate] = result;
}
}
}
首先,MessageTemplateCache类继承IMessageTemplateParser接口,该接口位于Core文件夹下,表示是一个解析字符串模板的核心接口,内部包含解析函数Parse,该函数的输入是字符串模板的字符串数据,输出是MessageTemplate类。其次,看下继承类MessageTemplateCache的实现,从名称上来看,可以看出它带有缓存的解析。当然,内部的实现也是这样的,在该类内部,有一个_innerParser的同类接口对象,感觉有点熟悉。继续往下,_templates是一个哈希表,它是字典类的非泛型实现,通过它可以寻找字符串模板对应的MessageTemplate对象,可以将其看成是一个缓存。构造函数附带一个对应消息解析对象,并给_innerParser赋值。在其核心的Parser方法中,它给出了具体的解析逻辑:
- 如果当前字符串的解析数据被哈希表所记录下来,那么直接从对应的位置提取解析好的
MessageTemplate对象并返回。 - 如果没有,则利用内部维护的
_innerParser对其解析 - 将解析后的
MessageTemplate对象添加到哈希表中,为后续同一个消息模板中提供缓存数据。
可以发现,这种代码结构和之前的 Sink 逻辑非常像,它也是装饰模式的一个实现。即无论采用何种具体解析消息模板的逻辑,通过MessageTemplateCache类可以为其动态添加缓存记录的功能,对于常用的消息模板场合下可以提高解析的效率,缩短运行时间。换句话来说,解析这一操作行为是一个纯函数,即给定的输入就能给定输出,不存在副作用,该函数的处理结果可以缓存下来。
MessageTemplateParser类
那么在 Serilog 有提供具体的解析类么?有的,它是位于 Parsing 文件夹下的MessageTemplateParser类。
public class MessageTemplateParser : IMessageTemplateParser
{
public MessageTemplate Parse(string messageTemplate)
{
...
return new MessageTemplate(messageTemplate, Tokenize(messageTemplate));
}
}
可以看到,这个类做的就是直接构造对应的MessageTemplate类对象,这里的Tokenize函数则是将字符串模板转换成一个或多个MessageTemplateToken对象,其核心思想就是从左到右依次扫描字符串中的每个字符,判断其是否是属性Token起始的{,然后将其分割。如果感兴趣的话请阅读具体源码,考虑到这段代码是一个过程性代码,通过调试一步步读下去即可,这里就不进行详述了。
总结
本篇主要讲述字符串解析过程的代码结构,该结构较为简单,模板解析的数据均保存在MessageTemplate类中,主要以MessageTemplateToken类对象的形式存在。解析后的 Token 主要分为两类,只用于描述文本信息的TextToken类以及描述属性数据的PropertyToken类。整个字符串模板通过MessageTemplateProcessor的Process函数进行解析,而其内部,利用装饰模式给处理行为添加缓存机制,即MessageTemplateCache类,真正的解析处理逻辑则放在MessageTemplateParser类中,同时这两个类实现IMessageTemplateParser接口,方便第三方进行替换。
这篇文章主要注重对模板数据的解析,然而,在日志记录的过程中,除了日志模板外,日志记录通常还会输入一些日志数据,这些数据常用来替换属性 Token 中的文本。在下一篇中,我们将着重研究 Serilog 日志库是如何处理这些日志数据的。
Serilog 源码解析——解析字符串模板的更多相关文章
- Serilog源码解析——使用方法
在上两篇文章(链接1和链接2)中,我们通过一个简易 demo 了解到了一个简单的日志记录类库所需要的功能,即一条日志有哪些数据,以及如何通过一次记录的方式将同一条日志消息记录到多个日志媒介中.在本文中 ...
- Serilog 源码解析——总览
背景 大家好,考虑到在最近这些天,闲来无事,找了个类库好好研究下别人写的高质量代码,颇有收获,打算和大家分享下.考虑到最近在自学 ASP.NET Core 的相关开发,对 Serilog 这个日志记录 ...
- mybatis 3.x源码深度解析与最佳实践(最完整原创)
mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...
- Spring框架之beans源码完全解析
导读:Spring可以说是Java企业开发里最重要的技术.而Spring两大核心IOC(Inversion of Control控制反转)和AOP(Aspect Oriented Programmin ...
- Spring框架之spring-web web源码完全解析
Spring框架之spring-web web源码完全解析 spring-web是Spring webMVC的基础,由http.remoting.web三部分组成,核心为web模块.http模块封装了 ...
- Spring框架之spring-webmvc源码完全解析
Spring框架之spring-webmvc源码完全解析 Spring框架提供了构建Web应用程序的全功能MVC模块.Spring MVC分离了控制器.模型对象.分派器以及处理程序对象的角色,支持多种 ...
- Spring框架之jdbc源码完全解析
Spring框架之jdbc源码完全解析 Spring JDBC抽象框架所带来的价值将在以下几个方面得以体现: 1.指定数据库连接参数 2.打开数据库连接 3.声明SQL语句 4.预编译并执行SQL语句 ...
- Spring框架之事务源码完全解析
Spring框架之事务源码完全解析 事务的定义及特性: 事务是并发控制的单元,是用户定义的一个操作序列.这些操作要么都做,要么都不做,是一个不可分割的工作单位.通过事务将逻辑相关的一组操作绑定在一 ...
- Thrift之代码生成器Compiler原理及源码详细解析1
我的新浪微博:http://weibo.com/freshairbrucewoo. 欢迎大家相互交流,共同提高技术. 又很久没有写博客了,最近忙着研究GlusterFS,本来周末打算写几篇博客的,但是 ...
- 《淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树》
淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树 曾经的学渣 2014-06-05 18:38:00 浏览1455 云数据库Oceanbase OceanBase是 ...
随机推荐
- 介绍了ASP。净样板
下载sample application (or see on Github) 内容 问题介绍什么是ASP.NET样板文件NET Boilerplate不是开始创建空的web应用程序从模板域层 关于名 ...
- httpd之ab压力测试
安装软件 yum install -y httpd 参数说明:用法Usage: ab [options] [http[s]://]hostname[:port]/path用法:ab [选项] 地址 选 ...
- C# 中 System.Range 结构体
翻译自 John Demetriou 2020年4月6日 的文章 <C# 8 Is Introducing Ranges> 我们之前讨论过的 C# 中的一个特性 System.Index ...
- Linux操作系统(第二版)(RHEL 8/CentOS 8)——勘误表
Linux操作系统(第二版)(RHEL 8/CentOS 8)--勘误表 http://www.tup.tsinghua.edu.cn/booksCenter/book_08172501.html 本 ...
- object-fit 详解
contain 被替换的内容将被缩放,以在填充元素的内容框时保持其宽高比. 整个对象在填充盒子的同时保留其长宽比,因此如果宽高比与框的宽高比不匹配,该对象将被添加"黑边". cov ...
- Python+Appium自动化测试(7)-截图方法
一,selenium模块的两种截图方法 get_screenshot_as_file(filename) 参数filename为截图文件保存的绝对路径,如: driver.get_screenshot ...
- 资源管理神器Clover
开开心心地上班,这时你得打开我的电脑,点进D盘,打开某个项目;然后还得打开XX文档,还有.... 最后的最后,你的桌面便成了这个样子 每天你都得天打开多个文件夹,切换时找文件找的晕头转向而烦恼. 每天 ...
- python文件管道 下载图集
# -*- coding: utf-8 -*- import re from time import sleep import scrapy from scrapy.linkextractors im ...
- 第三章 虚拟机的简单使用及其xshell远程工具的使用
一.虚拟机的快照 1.虚拟机的几种状态: 开机状态 === 运行状态 关机状态 挂起状态 === 虚拟机不关机,但是你使用不了 定身术 快照就是虚拟机的某种状态 === 月光宝盒 2.快照分类: 开机 ...
- The path "" is not a valid path to the 3.10.0-957.el7.x86_64 kernel headers.
安装 kernel-devel yum install kernel-devel-$(uname -r)