從微軟推出第一個版本的.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: {
3: public TraceSourceLogger(TraceSource traceSource);
4: public IDisposable BeginScope<TState>(TState state);
5: public bool IsEnabled(LogLevel logLevel);
6: public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func<TState, Exception, string> formatter);
7: }
當我們調用TraceSource的TraceEvent方法來寫追蹤日志的時候,需要指定追蹤日志的事件類型,該類型由提供的日志等級來決定,下表展示了日志等級與跟蹤事件類型之間的映射關系很簡單。 由于TraceSource通過調用其SourceSwitch的ShouldTrace方法來決定是否真正需要寫入當前分發的追蹤日志消息,所以當TraceSourceLogger的IsEnabled方法被調用的時候,它也會按照這樣的映射關系將指定的日志等級轉換成追蹤事件類型,并將其作為參數調用這個ShouldTrace方法,這個方法的返回值就是IsEnabled方法的返回值。
日志等級 |
跟蹤事件類型 |
Trace |
Verbose |
Debug |
Verbose |
Information |
Information |
Warning |
Warning |
Error |
Error |
Critical |
Critical |
TraceSourceLogger的BeginScope<TState>方法會返回一個TraceSourceScope對象,雖然這是一個共有的類型,但是這個對象并不做任何作用域的控制,其自身也不攜帶任何關于當前日志上下文的信息,所以TraceSourceLogger和前面介紹的DebugLogger和EventLogLogger一樣,其實都不提供針對日志上下文的支持。
三、TraceSourceLoggerProvider
TraceSourceLogger對應的LoggerProvider類型為TraceSourceLoggerProvider。如下面的代碼片段所示,當我們創建一個TraceSourceLoggerProvider對象時需要提供一個SourceSwitch和TraceListener對象(可選)。在實現的CreateLogger方法中,TraceSourceLoggerProvider會根據指定的名稱創建一個TraceSource對象,它將采用初始化時指定的SourceSwitch,預先指定的TraceListener也會注冊到這個TraceSource對象上,CreateLogger方法最終返回的將是根據這個TraceSource創建的TraceSourceLogger。
1: public class TraceSourceLoggerProvider : ILoggerProvider
2: {
3: public TraceSourceLoggerProvider(SourceSwitch rootSourceSwitch);
4: public TraceSourceLoggerProvider(SourceSwitch rootSourceSwitch, TraceListener rootTraceListener);
5:
6: public ILogger CreateLogger(string name);
7: public void Dispose();
8: }
值得一提的是TraceSourceLoggerProvider并不會在CreateLogger方法中頻繁地創建TraceSource對象,而是選擇將創建的TraceSource會根據指定的名稱被緩存起來。所以當CreateLogger方法被調用的時候,TraceSourceLoggerProvider會根據指定的名稱查看緩存中是否存在一個現成的TraceSource,如果存在則直接根據它創建返回的TraceSourceLogger。只有在確定同名的TraceSource不曾創建的情況下,新的TraceSource才會被真正創建出來。我們可以調用如下兩個擴展方法AddTraceSource根據指定的SourceSwitch(或者它的名稱)和TraceListener來創建TraceSourceLoggerProvider并將其注冊到指定的LoggerFactory上。
1: public static class TraceSourceFactoryExtensions
2: {
3: public static ILoggerFactory AddTraceSource(this ILoggerFactory factory, SourceSwitch sourceSwitch, TraceListener listener);
4: public static ILoggerFactory AddTraceSource(this ILoggerFactory factory, string switchName, TraceListener listener);
5: }
接下來我們通過一個簡單的實例來演示針對DebugLogger的日志記錄。我們創建一個空的控制臺應用,在添加必要的依賴之后,我們在Main方法中編寫了如下一段程序。如下面的代碼片段所示,我們采用依賴注入的方式創建了一個LoggerFactory,并調用擴展方法AddTraceSource方法創建并注冊了一個TraceSourceLoggerProvider對象。在利用LoggerFactory創建出Logger對象之后,我們利用后者記錄了三條日志消息。
1: public class Program
2: {
3: public static void Main(string[] args)
4: {
5: //注冊EncodingProvider實現對中文編碼的支持
6: Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
7:
8: ILogger logger = new ServiceCollection()
9: .AddLogging()
10: .BuildServiceProvider()
11: .GetService<ILoggerFactory>()
12: .AddTraceSource(new SourceSwitch(nameof(Program), "Warning"), new ConsoleTraceListener())
13: .CreateLogger<Program>();
14:
15:
16: int eventId = 3721;
17:
18: logger.LogInformation(eventId, "升級到最新.NET Core版本({version})", "1.0.0");
19: logger.LogWarning(eventId, "并發量接近上限({maximum}) ", 200);
20: logger.LogError(eventId, "數據庫連接失敗(數據庫:{Database},用戶名:{User})", "TestDb", "sa");
21: }
22: }
我們在調用擴展方法AddTraceSource創建并注冊TraceSourceLoggerProvider是指定了一個針對Warning等級的SourceSwitch,而指定的TraceListener是一個自定義的ConsoleTraceListener,所以只有兩條等級不低于Warning的日志消息會被這個ConsoleTraceListener按照上圖所示的形式輸出到控制臺上。
.NET Core的日志[1]:采用統一的模式記錄日志
.NET Core的日志[2]:將日志寫入控制臺
.NET Core的日志[3]:將日志寫入Debug窗口
.NET Core的日志[4]:利用EventLog寫日志
.NET Core的日志[5]:利用TraceSource寫日志
文章列表