文章出處

記錄各種級別的日志是所有應用不可或缺的功能。關于日志記錄的實現,我們有太多第三方框架可供選擇,比如Log4Net、NLog、Loggr和Serilog 等,當然我們還可以選擇微軟原生的診斷框架(相關API定義在命名空間“System.Diagnostics”中)實現對日志的記錄。.NET Core提供了獨立的日志模型使我們可以采用統一的API來完成針對日志記錄的編程,我們同時也可以利用其擴展點對這個模型進行定制,比如可以將上述這些成熟的日志框架整合到我們的應用中。 [ 本文已經同步到《ASP.NET Core框架揭秘》之中]

目錄
一、日志模型三要素
二、將日志寫入不同的目的地
三、采用依賴注入編程模式創建Logger
四、根據等級過濾日志消息

一、日志模型三要素

日志記錄編程主要會涉及到三個核心對象,它們分別是Logger、LoggerFactory和LoggerProvider,這三個對象同時也是.NET Core日志模型中的核心對象,并通過相應的接口(ILogger、ILoggerFactory和ILoggerProvider)來表示。對于日志模型的這個三個核心對象之間具有如下圖所示的關系,我們不難看出,LoggerFactory和LoggerProvider都是Logger的創建者, 而Loggerrovider卻注冊到LoggerFactory。單單從這個簡單的描述來看,我想很多人會覺得這個三個對象之間的關系很“混亂”,混亂的關系主要體現在Logger具有兩個不同的創建者。

image

LoggerProvider和LoggerFactory創建的其實是不同的Logger。LoggerProvider創建的Logger提供真正的日志寫入功能,即它的作用就是將提供的日志消息寫到對應的目的地(比如文件、數據庫等)。LoggerFactory創建的實際上一個“組合式”的Logger,換句話說,這個Logger實際上是對一組Logger的封裝,它自身并不提供真正的日志寫入功能,而是委托這組內部封裝的Logger來寫日志。

一個LoggerFactory對象上可以注冊多個LoggerProvider對象。在進行日志編程的時候,我們會利用LoggerFactory對象創建Logger來寫日志,而這個Logger對象內部封裝的Logger則通過注冊到LoggerFactory上的這些LoggerProvider來提供。如果我們將上圖1所示的關系采用下圖的形式來表示,日日志模型中這三個核心要素之間的關系就顯得很清楚了。

2

二、將日志寫入不同的目的地

接下來我們通過一個簡單的實例來演示如何將具有不同等級的日志寫入兩種不同的目的地,其中一種是直接將格式化的日志消息輸出到當前控制臺,另一種則是將日志寫入Debug輸出窗口(相當于直接調用Debug.WriteLine方法),針對這兩種日志目的地的Logger分別通過ConsoleLoggerProvider和DebugLoggerProvider這兩種不同的LoggerProvider來提供。

我們創建一個空的控制臺應用,并在其project.json文件中添加如下四個NuGet包的依賴。其中默認使用的LoggerFactory和由它創建的Logger定義在“Microsoft.Extensions.Logging”這個NuGet包中。而上述的這兩個LoggerProvider類型(ConsoleLoggerProvider和DebugLoggerProvider)分別定義在其余兩個NuGet包(“Microsoft.Extensions.Logging.Console”和“Microsoft.Extensions.Logging.Debug”)中。除此之外,由于.NET Core在默認情況下并不支持中文編碼,我們不得不程序啟動的時候顯式注冊一個支持中文編碼的EncodingProvider,后者定義在NuGet包 “System.Text.Encoding.CodePages”之中,所以我們需要添加這個這NuGet包的依賴。

   1: {
   2:   ...
   3:   "dependencies": {
   4:     ...
   5:     "Microsoft.Extensions.Logging"             : "1.0.0",
   6:     "Microsoft.Extensions.Logging.Console"     : "1.0.0",
   7:     "Microsoft.Extensions.Logging.Debug"       : "1.0.0",
   8:     "System.Text.Encoding.CodePages"           : "4.0.1"
   9:   },
  10:   

日志記錄通過如下一段程序來完成。如下面的代碼片段所示,我們首先創建一個LoggerFactory對象,并先后通過調用AddProvider方法將一個ConsoleLoggerProvider對象和一個DebugLoggerProvider對象注冊到它之上。創建這兩個LoggerProvider所調用的構造函數具有一個Func<string, LogLevel, bool>類型的參數,該委托對象的兩個輸入參數分別代表日志消息的類型和等級,布爾類型的返回值決定了創建的Logger是否真的會寫入給定的日志消息。由于我們傳入的委托對象總是返回True,意味著提供的所有日志均會被這兩個LoggerProvider創建的Logger對象寫入對應的目的地。

   1: public class Program
   2: {
   3:     public static void Main(string[] args)
   4:     {
   5:         //注冊EncodingProvider實現對中文編碼的支持
   6:         Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
   7:  
   8:         Func<string, LogLevel, bool> filter = (category, level) => true;
   9:  
  10:         ILoggerFactory loggerFactory = new LoggerFactory();
  11:         loggerFactory.AddProvider(new ConsoleLoggerProvider(filter, false));
  12:         loggerFactory.AddProvider(new DebugLoggerProvider(filter));
  13:         ILogger logger = loggerFactory.CreateLogger(nameof(Program));
  14:  
  15:         int eventId = 3721;
  16:  
  17:         logger.LogInformation(eventId, "升級到最新.NET Core版本({version})", "1.0.0");
  18:         logger.LogWarning(eventId, "并發量接近上限({maximum}) ", 200);
  19:         logger.LogError(eventId, "數據庫連接失敗(數據庫:{Database},用戶名:{User})", "TestDb", "sa");
  20:  
  21:     }
  22: }

在完成針對LoggerProvider的注冊之后,我們通過指定日志類型(“Program”)調用LoggerFactory對象的CreateLogger方法創建一個Logger對象,然后先后調用LogInformation、LogWarning和LogError這三個擴展方法記錄三條日志消息,這三個方法的命名決定了日志的采用的等級(Information、Warning和Error)。我們在調用這三個方法的時候指定了一個表示日志記錄事件ID的整數(3721),以及具有占位符(“{version}”、“{maximum}”、“{Database}”和“{User}”)的消息模板和替換這些占位符的參數列表。

由于ConsoleLoggerProvider被注冊到創建Logger的LoggerFactory上,所以當我們執行這個實例程序之后,三條日志消息會直接按照如下的形式打印到控制臺上。我們可以看出格式化的日志消息不僅僅包含我們指定的消息內容,日志的等級、類型和事件ID同樣包含其中。不僅如此,表示日志等級的文字還會采用不同的前景色和背景色來顯示。

3

由于LoggerFactory上還注冊了另一個DebugLoggerProvider對象,它創建的Logger會直接調用Debug.WriteLine方法寫入格式化的日志消息。所以當我們以Debug模式編譯并執行該程序時,Visual Studio的輸出窗口會以如下圖所示的形式呈現出格式化的日志消息。

4

上面這個實例演示了日志記錄采用的基本編程模式:首先創建或者獲取一個LoggerFactory并根據需要注冊相應的LoggerProvider,然后利用LoggerFactory創建的Logger來記錄日志。我們可以直接調用AddProvider方法將指定的LoggerProvider注冊到某個LoggerFactory對象上,除此之外,絕大部分LoggerFactory都具有相應的擴展方法使我們可以采用更加簡潔的代碼來完成針對它們的注冊。比如在如下所示的代碼片斷中,我們可以直接調用針對ILoggerFactory接口的擴展方法AddConsole和AddDebug分別完成針對ConsoleLoggerProvider和DebugLoggerProvider的注冊。

   1: ILogger logger = new LoggerFactory()
   2:     .AddConsole()
   3:     .AddDebug()
   4:     .CreateLogger(nameof(Program));

三、采用依賴注入編程模式創建Logger

在我們演示的實例中,我們直接調用構造函數創建了一個LoggerFactory并利用它來創建用于記錄日志的Logger,但是在一個ASP.NET Core應用中,我們總是依賴注入的方式來獲取這個LoggerFactory對象。為了演示針對依賴注入的LoggerFactory獲取方式,我們首先需要作的是在project.json文件中按照如下的方式添加針對“Microsoft.Extensions.DependencyInjection”這個NuGet包的依賴。

   1: {
   2:   "dependencies": {
   3:     ...
   4:     "Microsoft.Extensions.DependencyInjection"    : "1.0.0",
   5:     "Microsoft.Extensions.Logging"                : "1.0.0",
   6:     "Microsoft.Extensions.Logging.Console"        : "1.0.0",
   7:     "Microsoft.Extensions.Logging.Debug"          : "1.0.0",
   8:   },
   9:   ...
  10: }

所謂采用依賴注入的方式得到用于注冊LoggerProvider和創建Logger的LoggerFactory,本質上就是采用調用ServiceProvider的GetService方法得到這個對象。如果希望ServiceProvider能夠指定的類型(ILoggerFactory接口)得到我們所需的LoggerFactory,在這之前必須在創建ServiceProvider的ServiceCollection上作相應的服務注冊。針對LoggerFactory的注冊可以通過調用針對IServiceCollection接口的擴展方法AddLogging來完成。對于我們演示實例中使用的Logger對象,可以利用以依賴注入形式獲取的LoggerFactory來創建,如下所示的代碼片斷體現了這樣的編程方式。

   1: ILogger logger = new ServiceCollection()
   2:     .AddLogging()
   3:     .BuildServiceProvider()
   4:     .GetService<ILoggerFactory>()
   5:     .AddConsole()
   6:     .AddDebug()
   7:     .CreateLogger(nameof(Program));

四、根據等級過濾日志消息

由于同一個LoggerFactory上可以注冊多個LoggerProvider,所以當我們利用LoggerFactory創建出相應的Logger用它來寫入某條日志消息的時候,這條消息實際上會分發給由LoggerProvider提供的所有Logger。其實在很多情況下,我們并不希望每個Logger都去寫入分發給它的每條日志消息,而是希望Logger能夠“智能”地忽略不應該由它寫入的日志消息。 每條日志消息都具有一個等級,針對日志等級是我們普遍采用的日志過濾策略。日志等級通過具有如下定義的枚舉LogLevel來表示,枚舉項的值決定了等級的高低,值越大,等級越高;等級越高,越需要記錄。

   1: public enum LogLevel
   2: {
   3:     Trace           = 0,
   4:     Debug           = 1,
   5:     Information     = 2,
   6:     Warning         = 3,
   7:     Error           = 4,
   8:     Critical        = 5,
   9:     None            = 6
  10: }

在前面介紹ConsoleLoggerProvider和DebugLoggerProvider的時候,我們提到可以在調用構造函數時可以傳入一個Func<string, LogLevel, bool>類型的參數來指定日志過濾條件。對于我們實例中寫入的三條日志,它們的等級由低到高分別是Information、Warning和Error,如果我們選擇只寫入等級高于或等于Warning的日志,可以采用如下的方式來創建對應的Logger。

   1: Func<string, LogLevel, bool> filter = (category, level) => level >= LogLevel.Warning;
   2:  
   3: ILoggerFactory loggerFactory = new LoggerFactory();
   4: loggerFactory.AddProvider(new ConsoleLoggerProvider(filter, false));
   5: loggerFactory.AddProvider(new DebugLoggerProvider(filter));
   6: ILogger logger = loggerFactory.CreateLogger(nameof(Program));

針對ILoggerFactory接口的擴展方法AddConsole和AddDebug同樣提供的相應的重載使我們可以通過傳入的Func<string, LogLevel, bool>類型的參數來提供日志過濾條件。除此之外,我們還可以直接指定一個類型為LogLevel的參數來指定過濾日志采用的最低等級。我們演示實例中的使用的Logger也可以按照如下兩種方式來創建。

   1: ILogger logger = new ServiceCollection()
   2:     .AddLogging()
   3:     .BuildServiceProvider()
   4:     .GetService<ILoggerFactory>()
   5:  
   6:     .AddConsole((c,l)=>l>= LogLevel.Warning)
   7:     .AddDebug((c, l) => l >= LogLevel.Warning)
   8:     .CreateLogger(nameof(Program));

或者

   1: ILogger logger = new ServiceCollection()
   2:     .AddLogging()
   3:     .BuildServiceProvider()
   4:     .GetService<ILoggerFactory>()
   5:     .AddConsole(LogLevel.Warning)
   6:     .AddDebug(LogLevel.Warning)
   7:     .CreateLogger(nameof(Program));

由于注冊到LoggerFactory上的ConsoleLoggerProvider和DebugLoggerProvider都采用了上述的日志過濾條件,所有由它們提供Logger都只會寫入等級為Warning和Error的兩條日志,等級為Information的那條則會自動忽略掉。所以我們的程序執行之后會在控制臺上打印出如下圖所示的日志消息。

5

 


.NET Core的日志[1]:采用統一的模式記錄日志
.NET Core的日志[2]:將日志寫入控制臺
.NET Core的日志[3]:將日志寫入Debug窗口
.NET Core的日志[4]:利用EventLog寫日志
.NET Core的日志[5]:利用TraceSource寫日志


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()