.NET Core的日誌[1]:採用統一的模式記錄日誌

来源:http://www.cnblogs.com/artech/archive/2016/08/17/logging-for-net-core-01.html
-Advertisement-
Play Games

記錄各種級別的日誌是所有應用不可或缺的功能。關於日誌記錄的實現,我們有太多第三方框架可供選擇,比如Log4Net、NLog、Loggr和Serilog 等,當然我們還可以選擇微軟原生的診斷框架(相關API定義在命名空間“System.Diagnostics”中)實現對日誌的記錄。.NET Core提... ...


記錄各種級別的日誌是所有應用不可或缺的功能。關於日誌記錄的實現,我們有太多第三方框架可供選擇,比如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


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Linux作為操作系統,Apache作為Web伺服器,MySQL作為資料庫,PHP作為伺服器端腳本解釋器。由於這四個軟體都是免費或開放式源碼軟體,因此使用這種不用花一分錢(人工成本除外)就可以建立起一個穩定、免費的網站系統,被業界稱為“LAMP”組合。今天,我們就講講MySQL資料庫的安裝和簡單應用 ...
  • CoreOS Hyper-V 安裝, Install to disck 準備 安裝鏡像 https://coreos.com/releases/ 選擇版本, 點 Browse Images, 下載以下文件 (本文以 Alpha 為例) coreos_production_iso_image.iso ...
  • 輸入輸出重定向是在 linux shell 中經常使用的一個功能,本編博文簡單介紹了什麼是輸入輸出以及操作方法 ...
  • 這是之前在Linux下配置Node環境變數時踩過的坑,今天又有小伙伴質詢這個問題,因此記錄下來,不僅是給新童鞋們一些參考,也方便日後查閱 在這之前,相信都已經安裝好了,沒安裝的可以查看博主另一篇文章 http://www.cnblogs.com/Halifa/p/5772263.html 配置Nod ...
  • RVA是相對虛擬地址(Relative Virtual Address)的縮寫。RVA是當PE 文件被裝載到記憶體中後,某個數據位置相對於文件頭的偏移量。 例如:導入表的位置和大小可以從PE文件頭中IMAGE_OPTIONAL_HEADER32結構的數據目錄欄位中獲取,對應的項目是DataDirect... ...
  • RancherOS Hyper-V 安裝, Install to disk 打開 Hyper-V 管理界器, 新建虛擬機 輸入名稱和存儲位置 選擇一代 最低 1024M 配置網路 創建虛擬磁碟 設置啟動 ISO, 下載 創建完成 設置處理器 設置記憶體, 註: 如果使用動態記憶體, 需限定下最大記憶體 啟 ...
  • 關於 信號signal的知識鋪墊 點這裡 信號由三種處理方式: 如果信號的處理動作是用戶自定義函數,在信號遞達時就調用這個自定義函數,這稱為捕捉信號。 進程收到一個信號後不會被立即處理,而是在恰當時機進行處理!即內核態返回用戶態之前 ! 但是由於信號處理函數的代碼在用戶空間,所以這增加了內核處理信號 ...
  • 有兩個功能變數名稱指向我的網站,其中一個功能變數名稱訪問我的網站的話就可以看到日期控制項 另一個功能變數名稱訪問我的網站不能看到日期控制項, 在EF中使用日期控制項,瀏覽器審查元素後看到,報這個錯誤“Both must set "document.domain" to the same value to allow access ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...