1. 程式人生 > >Serilog 原始碼解析——資料的儲存(下)

Serilog 原始碼解析——資料的儲存(下)

[上一篇](https://www.cnblogs.com/iskcal/p/saving-of-log-data-2.html)中,我們提到了日誌資料是如何進行解析了。然而,Serilog 靈活採用了不同的策略(Policy)決定一個日誌物件如何解析到`LogEventPropertyValue`的子類物件中,即採用了`IScalarConversionPolicy`以及`IDestructingPolicy`介面對資料做轉換。在本篇中,著重強調這兩個介面以及其實現類是如何做到這一功能的。([系列目錄](https://www.cnblogs.com/iskcal/p/introduction-to-the-source-code-of-serilog.html#目錄)) ## `IScalarConversionPolicy`介面 ```csharp interface IScalarConversionPolicy { bool TryConvertToScalar(object value, out ScalarValue result); } ``` `IScalarConversionPolicy`這個介面用來負責將日誌資料轉換成`ScalarValue`的,這點從輸入輸出引數就可以看的出來,`value`作為日誌的輸入資料,其型別為可接受任意資料物件的`object`根類,而轉換後的`result`變數採用`out`形式的輸入引數,而引數的返回值為布林型別,執行該轉換成功與否。這種函式設計模式和 C# 中基礎型別轉換函式`TryParse`類似,二者均將重要的資料以輸入引數做傳遞,返回值僅指明當前處理方式是否成功。 ### `ByteArrayScalarConversionPolicy`類 Serilog 中`IScalarConversionPolicy`介面的實現類有3個,這裡首先介紹第一個實現類:`ByteArrayScalarConversionPolicy`。從名字上來看,大體就是位元組陣列轉換到`ScalarValue`這一功能。具體看下原始碼: ```csharp 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`的一個策略類。從名字上來看,它的作用物件是列舉。 ```csharp 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`類 該類主要處理的是將認定為簡單的資料型別進行包裹。 ```csharp class SimpleScalarConversionPolicy : IScalarConversionPolicy { readonly HashSet _scalarTypes; public SimpleScalarConversionPolicy(IEnumerable scalarTypes) { _scalarTypes = new HashSet(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`類,其功能和上述介面沒有什麼區別) ```csharp public interface IDestructuringPolicy { bool TryDestructure(object value, ILogEventPropertyValueFactory propertyValueFactory, out LogEventPropertyValue result); } ``` 該介面內的函式和上一個介面內函式類似,有輸入的日誌資料`value`,轉換後的`result`資料,以及函式布林返回值。除此之外,還有一個`propertyValueFactory`,函式的註釋對此描述為,遞迴採用策略來解構新的值,不是很明白它的意思,我們具體看實現類是如何操作的。 ### `DelegateDestrcuturingPolicy`類 ```csharp 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`類 ```csharp class ProjectedDestructuringPolicy : IDestructuringPolicy { readonly Func _canApply; readonly Func _projection; public ProjectedDestructuringPolicy(Func canApply, Func 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 中通用的渲染