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

本篇要解决什么

之前提到,在Logger类中构造对应的LogEvent对象之前,日志记录器通过MessageTemplateProcessor类对象的Process方法处理字符串模板和传入进来的数据信息。这个方法内部只是做了两件事:

  1. 解析消息模板,分析哪些是字符串字面值哪些是需要转换的属性值
  2. 构造相关的数据对象
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类对象内所拥有的NamePropertiesPositionProperties均描述一组属性 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,PropertyNameversionDestructuring值为Default,Format值为000Alignment为默认值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方法中,它给出了具体的解析逻辑:

  1. 如果当前字符串的解析数据被哈希表所记录下来,那么直接从对应的位置提取解析好的MessageTemplate对象并返回。
  2. 如果没有,则利用内部维护的_innerParser对其解析
  3. 将解析后的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类。整个字符串模板通过MessageTemplateProcessorProcess函数进行解析,而其内部,利用装饰模式给处理行为添加缓存机制,即MessageTemplateCache类,真正的解析处理逻辑则放在MessageTemplateParser类中,同时这两个类实现IMessageTemplateParser接口,方便第三方进行替换。

这篇文章主要注重对模板数据的解析,然而,在日志记录的过程中,除了日志模板外,日志记录通常还会输入一些日志数据,这些数据常用来替换属性 Token 中的文本。在下一篇中,我们将着重研究 Serilog 日志库是如何处理这些日志数据的。

Serilog 源码解析——解析字符串模板的更多相关文章

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

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

  2. Serilog 源码解析——总览

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

  3. mybatis 3.x源码深度解析与最佳实践(最完整原创)

    mybatis 3.x源码深度解析与最佳实践 1 环境准备 1.1 mybatis介绍以及框架源码的学习目标 1.2 本系列源码解析的方式 1.3 环境搭建 1.4 从Hello World开始 2 ...

  4. Spring框架之beans源码完全解析

    导读:Spring可以说是Java企业开发里最重要的技术.而Spring两大核心IOC(Inversion of Control控制反转)和AOP(Aspect Oriented Programmin ...

  5. Spring框架之spring-web web源码完全解析

    Spring框架之spring-web web源码完全解析 spring-web是Spring webMVC的基础,由http.remoting.web三部分组成,核心为web模块.http模块封装了 ...

  6. Spring框架之spring-webmvc源码完全解析

    Spring框架之spring-webmvc源码完全解析 Spring框架提供了构建Web应用程序的全功能MVC模块.Spring MVC分离了控制器.模型对象.分派器以及处理程序对象的角色,支持多种 ...

  7. Spring框架之jdbc源码完全解析

    Spring框架之jdbc源码完全解析 Spring JDBC抽象框架所带来的价值将在以下几个方面得以体现: 1.指定数据库连接参数 2.打开数据库连接 3.声明SQL语句 4.预编译并执行SQL语句 ...

  8. Spring框架之事务源码完全解析

    Spring框架之事务源码完全解析   事务的定义及特性: 事务是并发控制的单元,是用户定义的一个操作序列.这些操作要么都做,要么都不做,是一个不可分割的工作单位.通过事务将逻辑相关的一组操作绑定在一 ...

  9. Thrift之代码生成器Compiler原理及源码详细解析1

    我的新浪微博:http://weibo.com/freshairbrucewoo. 欢迎大家相互交流,共同提高技术. 又很久没有写博客了,最近忙着研究GlusterFS,本来周末打算写几篇博客的,但是 ...

  10. 《淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树》

    淘宝数据库OceanBase SQL编译器部分 源码阅读--解析SQL语法树   曾经的学渣 2014-06-05 18:38:00 浏览1455 云数据库Oceanbase   OceanBase是 ...

随机推荐

  1. Solon集成(02)- 轻松吃下小馒头 Dubbo

    Solon详解系列文章: Solon详解(一)- 快速入门 Solon详解(二)- Solon的核心 Solon详解(三)- Solon的web开发 Solon详解(四)- Solon的事务传播机制 ...

  2. CN1,CN2 GT和CN2 GIA的区别

    用一句话来概括,CN1主要定位于承载普通质量的互联网业务,而CN2则定位于承载企业VPN业务.中国电信的自营业务及高质量的互联网业务,CN2 GIA又比GT要好一些. 顺序:CN2 GIA>CN ...

  3. linux 内存泄露检测工具

    Valgrind Memcheck 一个强大开源的程序检测工具 下载地址:http://valgrind.org/downloads/current.html Valgrind快速入门指南:http: ...

  4. 关于keytool和jarsigner工具签名的使用小结

    在我们日常Android应用开发中,我们都要对我们开发的apk做签名处理,或者加固,增强我们apk的安全性,防止被逆向反编译,在apk签名这块,我们一般采用JDK自动工具来签名,下面就对相关工具做个简 ...

  5. shell脚本获取随机数

    $RANDOM系统变量 在bash中,支持$RANDOM系统变量,范围是 [0, 32767] #!/bin/bash set -e randN() { local N=$1 echo $(($RAN ...

  6. web自动化测试总结

    web自动化: 1.测试用例(操作步骤,熟读需求文档,web项目先用手工研究,前置条件,预期结果) 接口自动化测试中数据功能最适合作为数据驱动,数据放在excel中需要操作excel 为什么web自动 ...

  7. trade可撤销贪心正确性证明

    鉴于tarde这道题正解过于好写,导致我对这个诡异的贪心的正确性产生了疑问,所以花了2h的时间与同机房神犇M-Blanca,Midoria7,goote~进行讨论,最后与goote~犇犇各得出了一个正 ...

  8. nginx优化:配置gzip压缩页面提高访问速度(nginx1.18.0)

    一,为什么nginx要使用gzip 1,压缩的作用: 页面使用gzip压缩之后, 页面大小可以压缩到原来的1/7左右, 传输速度和页面打开时间都可以大幅度提高, 有利于用户访问页面体验的提升 2,Ng ...

  9. 在WPF中一种较好的绑定Enums数据方法

    引言 在你使用wpf应用程序开发的时候,是否需要进行数据绑定到Enum数据呢?在这篇文章中,我将向你展示在WPF中处理Enum数据绑定的方法. 假设存在一个这样的Enum数据的定义,具体内容如下文代码 ...

  10. java前后端开发需掌握的框架及技术

    一.Java开发 1.J2EE架构及主流框架,spring4.spring boot.spring MVC.spring Security.spring cloud.struct2.hibernate ...