• 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;
        }
    }
    

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

    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 中通用的渲染方法。

  • 相关阅读:
    MySql模糊查询like通配符使用详细介绍
    使用powershell批量添加Qt的文件(生成pro)
    宏定义的教训
    使用powershell批量添加Keil和IAR的头文件路径
    python和数据科学(Anaconda)
    CDCE913产生任意频率
    QT中检索设定目录下所有指定文件的方法
    QT中将ASCII转换为对应数值的方法
    STM8如何使用自带的bootloader
    QT中使用函数指针
  • 原文地址:https://www.cnblogs.com/iskcal/p/saving-of-log-data-3.html
Copyright © 2020-2023  润新知