從微軟推出第一個版本的.NET Framework的時候,就在“System.Diagnostics”命名空間中提供了Debug和Trace兩個類幫助我們完成針對調試和跟蹤信息的日誌記錄。在.NET Framework 2.0中,微軟引入了TraceSource並對跟蹤日誌系統進行了優化,優化後的跟... ...
從微軟推出第一個版本的.NET Framework的時候,就在“System.Diagnostics”命名空間中提供了Debug和Trace兩個類幫助我們完成針對調試和跟蹤信息的日誌記錄。在.NET Framework 2.0中,微軟引入了TraceSource並對跟蹤日誌系統進行了優化,優化後的跟蹤日誌系統在.NET Core中又經過了相應的簡化。.NET Core的日誌模型藉助TraceSourceLoggerProvider實現對TraceSource的整合,在正式介紹這個Logger之前,我們先來認識一下TraceSource跟蹤日誌系統中的三個核心對象。[ 本文已經同步到《ASP.NET Core框架揭秘》之中]
目錄
一、基於TraceSource的追蹤日誌系統
二、TraceSourceLogger
三、TraceSourceLoggerProvider
一、基於TraceSource的追蹤日誌系統
對於這個基於TraceSource的跟蹤日誌系統來說,除了TraceSource之外,它還具有額外連個核心的對象,它們分別是TraceListener和SourceSwitch,三者之間的關係如下圖所示。日誌消息的寫入實現在TraceListener上,我們可以將一組TraceListener註冊到某個TraceSource之上。當我們利用TraceSource記錄某條跟蹤日誌時,日誌消息會分發給註冊的每一個TraceListener並由它們將日誌消息寫到對應的目的地。每個TraceSource都具有一個SourceSwitch,後者起到了日誌過濾的作用。具體來說,SourceSwitch定義了相應的過濾條件來幫助TraceSource決定是否應該將跟蹤日誌分發給TraceListener,如果指定的日誌消息不滿足過濾條件,TraceSource將不會進行任何實質性的日誌記錄工作。
如下所示的是TraceSource的定義。每一個TraceSource都具有一個名稱,它一般代表寫入跟蹤日誌的應用程式、服務或者組件的名稱。我們可以調用它的三組Trace方法(TraceData、TraceEvent和TraceInformation)來記錄跟蹤日誌。由於這些方法都標註了一個ConditionaleAttribute特性並將條件編譯符“TRACE”,所以針對這些方法的調用只有在針對Trace模式編譯的應用中才是有效的。
1: public class TraceSource
2: {
3: public TraceListenerCollection Listeners { get; }
4: public string Name { get; }
5: public SourceSwitch Switch { get; set; }
6:
7: public TraceSource(string name);
8: public TraceSource(string name, SourceLevels defaultLevel);
9:
10: [Conditional("TRACE")]
11: public void TraceData(TraceEventType eventType, int id, object data);
12: [Conditional("TRACE")]
13: public void TraceData(TraceEventType eventType, int id, params object[] data);
14:
15: [Conditional("TRACE")]
16: public void TraceEvent(TraceEventType eventType, int id);
17: [Conditional("TRACE")]
18: public void TraceEvent(TraceEventType eventType, int id, string message);
19: [Conditional("TRACE")]
20: public void TraceEvent(TraceEventType eventType, int id, string format, params object[] args);
21:
22: [Conditional("TRACE")]
23: public void TraceInformation(string message);
24: [Conditional("TRACE")]
25: public void TraceInformation(string format, params object[] args);
26: }
通過TraceData、TraceEvent和TraceInformation這三個方法記錄的跟蹤日誌都具有一個通過枚舉類型TraceEventType表示的事件類型,它相當於前面提到的日誌等級。TraceEventType的這些枚舉項的值越小意味著等級越高,定義日誌等級的LogLevel則於此相反。在調用TraceData和TraceEvent方法時,我們需要顯式地為寫入的跟蹤日誌指定事件類型,而TraceInformation方法則預設使用Information類型。
1: public enum TraceEventType
2: {
3: Critical = 1,
4: Error = 2,
5: Warning = 4,
6: Information = 8,
7: Verbose = 16,
8: }
與TraceEventType枚舉對應的還具有另一個名為SourceLevels的枚舉,除了包含五種具體事件類型之外,還具有額外兩個選項All和Off,該枚舉對象被SourceSwitch用來過濾日誌。在調用構造函數創建TraceSource的時候,我們可以指定一個SourceLevels枚舉值作為預設的等級。如果這個等級未作顯式設置,創建的TraceSource採用的等級為Off,這意味著預設情況下針對追蹤日誌的記錄是禁止的。
1: [Flags]
2: public enum SourceLevels
3: {
4: All = -1,
5: Off = 0,
6: Critical = 1,
7: Error = 3,
8: Warning = 7
9: Information = 15,
10: Verbose = 31
11: }
我們創建的TraceSource是指定(或者預設設置)的表示日誌等級的SourceLevels枚舉會用來創建一個具有如下定義的SourceSwitch對象,TraceSource的Switch屬性返回的就是這麼一個對象。顧名思義,SourceSwitch是一個開關,它利用ShouldTrace方法決定了針對某種類型的跟蹤日誌的寫入操作是應該開啟還是關閉。如下麵的代碼片段所示,ShouldTrace方法返回的結果是根據通過Level屬性返回的跟蹤日誌等級計算出來的,表示跟蹤日誌等級的SourceLevels枚舉正是最初正是由TraceSource在初始化時提供的。
1: public class SourceSwitch : Switch
2: {
3: public SourceLevels Level {get;set;}
4:
5: public SourceSwitch(string name);
6: public SourceSwitch(string displayName, string defaultSwitchValue);
7:
8: public bool ShouldTrace(TraceEventType eventType)
9: {
10: return ((base.SwitchSetting & eventType) > 0);
11: }
12: }
TraceSource對象自身並不負責針對跟蹤日誌的寫入,它僅僅將日誌的寫入請求分發給註冊的TraceListener並委托它們來完成寫日誌的功能。這些註冊到TraceSource上的TraceListenter被保存到由它的Listeners屬性返回的集合對象中。所有的TraceListener都拍生於如下這個抽象的TraceListener類型,它定義瞭如下兩組TraceData和TraceEvent方法。當我們調用TraceSource的TraceData、TraceEvent和TraceInformation方法時,如果通過SourceSwitch判斷應該開啟針對當前跟蹤日誌的寫入功能,那麼註冊的TraceListener的TraceData或者TraceEvent方法將會被調用。
1: public abstract class TraceListener : IDisposable
2: {
3: ...
4: public virtual void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, object data);
5: public virtual void TraceData(TraceEventCache eventCache, string source, TraceEventType eventType, int id, params object[] data);
6:
7: public virtual void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id);
8: public virtual void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message);
9: public virtual void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args);
10: }
接下來我們通過一個簡單的控制台應用來演示如何創建一個TraceSource並使用它來記錄追蹤日誌。由於TraceSource定義在“System.Diagnostics.TraceSource”這個NuGet包中,我們需要在project.json文件中需要按照如下的方式添加針對這個NuGet包的依賴。和前面演示的實例一樣,為了提供針對中文編碼的支持,我們不得不添加針對“System.Text.Encoding.CodePages”這個NuGet包的依賴。
1: {
2: ...
3: "dependencies": {
4: "System.Diagnostics.TraceSource": "4.0.0",
5: "System.Text.Encoding.CodePages": "4.0.1"
6: }
7: }
由於TraceSource總是利用註冊在它上面的TraceListener來完成寫日誌的工作,所以我們按照如下的方式自定義了ConsoleTraceListener。顧名思義,ConsoleTraceListener旨在將分發給它的追蹤日誌輸出到控制臺上。如下麵的代碼片段所示,這個ConsoleTraceListener僅僅重寫了Write和WriteLine方法,它們調用定義在Console類型上的同名方法將格式化好的日誌消息輸出到控制臺上。
1: public class ConsoleTraceListener : TraceListener
2: {
3: public override void Write(string message) => Console.Write(message);
4: public override void WriteLine(string message) => Console.WriteLine(message);
5: }
我們在作為程式入口的Main方法中創建了一個TraceSource對象。在調用構造函數的時候,除了指定TraceSource的名稱(“Program”)之外,我們還設置了一個預設的追蹤日誌等級(Warning)。接下來我們創建了一個ConsoleTraceListener對象並將其註冊到TraceSource對象上。在此之後,我們調用TraceSource的TraceEvent方法記錄了三條追蹤日誌,它們採用的追蹤事件類型分別是Information、Warining和Error。
1: public class Program
2: {
3: public static void Main(string[] args)
4: {
5: //註冊EncodingProvider實現對中文編碼的支持
6: Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
7:
8: TraceSource traceSource = new TraceSource(nameof(Program), SourceLevels.Warning);
9: traceSource.Listeners.Add(new ConsoleTraceListener());
10:
11: int eventId = 3721;
12: traceSource.TraceEvent(TraceEventType.Information, eventId, "升級到最新.NET Core版本({0})", "1.0.0");
13: traceSource.TraceEvent(TraceEventType.Warning, eventId, "併發量接近上限({0}) ", 200);
14: traceSource.TraceEvent(TraceEventType.Error, eventId, "資料庫連接失敗(資料庫:{0},用戶名:{1})", "TestDb", "sa");
15: }
16: }
該程式運行之後,我們利用TraceSource記錄的追蹤日誌將會被註冊的ConsoleTraceListener按照如下圖所示的形式輸出到控制臺上。由於我們在創建TraceSource的時候指定了一個預設的追蹤日誌等級Warning,所以只有不低於這個等級的兩條日誌才會顯示在控制臺上。
二、TraceSourceLogger
.NET Core的日誌模型利用一個定義在NuGet包“Microsoft.Extensions.Logging.TraceSource”中的TraceSourceLogger類型實現與TraceSource跟蹤日誌系統的整合。從如下麵的代碼片段我們不難看出,一個TraceSourceLogger對象實際上就是對一個TraceSource對象的封裝,在實現的Log<State>方法中,它會調用TraceSource的TraceEvent方法來完成針對日誌消息的寫入工作。
1: public class TraceSourceLogger : ILogger
2: {