標籤:pen depend 參數說明 日誌記錄 class 樣本 exception 配置 shang
在之前我們簡單介紹了 .NET Core 中的 DI組件,沒來及瞭解的童鞋可以翻翻我之前的文章。
接下來會對 .NET Core 中的 Logging 進行介紹。
本文中使用了“Microsoft.Extensions.Logging.Console”做為輸出目標,後續文章會詳解。
Logging 中的三劍客
可以看到 Logging 的核心抽象就是三個介面,分別是:
ILogger:負責具體的日誌寫入邏輯,如:FileLogger,ConsoleLogger,SQLLogger,ElasticsearchLogger 等。
ILoggerProvider:用來建立記錄器,一般和Logger配套使用,相當於單個Logger類型的工廠介面。
ILoggerFactory:記錄器工廠,直接面向使用者的,使用者可以通過記錄器工廠添加記錄器提供者和建立記錄器。
這幾個核心抽象位於 NuGet包:“Microsoft.Extensions.Logging.Abstractions”中。
日誌等級
在.NET Core提供的日誌抽象中提供了7個日誌等級(比一般的日誌組件多提供了一個Trace和None),分別是:
Trace
包含最詳細訊息的日誌。 這些訊息可能包含敏感的應用程式資料。 預設情況下禁用這些訊息,並且不應在生產環境中啟用這些訊息。
Debug
在開發過程中用於互動式調查的日誌。 這些日誌應主要包含對調試有用的資訊,不具有長期價值。
Information
跟蹤應用程式的一般流程的日誌。 這些日誌應具有長期價值。
Warning
反白應用程式流程中異常或意外事件的日誌,但是否則不會導致應用程式執行停止。
Error
噹噹前執行流程由於失敗而停止時,會反白的日誌。這些應該指示當前活動中的故障,而不是應用程式範圍的故障。
Critical
描述不可恢複的應用程式或系統崩潰或災難性的日誌失敗需要立即關注。
None
不用於寫日誌訊息。 指定記錄類別不應寫任何訊息。
簡單的使用
CreateLogger 方法
CreateLogger 方法的簽名為
它提供了兩個擴充方法,可以通過類型作為分類名稱,如下:
如何根據類型確定分類名稱?
在擴充方法內部使用了“GetTypeDisplayName(Type type)”來根據類型擷取名稱(裡面有一些邏輯處理,但一般是採用“{命名空間}.{類型名稱}”作為分類名稱)。
實現傳送門:https://github.com/aspnet/Logging/blob/patch/1.1.3/src/Microsoft.Extensions.Logging.Abstractions/Internal/TypeNameHelper.cs
Log方法
logLevel
日誌等級,詳情見上文。
eventId(結構體,必填,可以傳入 0 或 default(EventId)來充當預設值)
事件ID。
這邊的事件ID是用來追蹤的,類似 ErrorCode、StatusCode。這樣在日誌檢索的時候可以通過code很方便的找到。
是一個結構體,預設為:“0”。
state(可為null)
狀態。
需要記錄的對象,這邊可以傳入任何類型,這就有點奇怪了日誌不都是字元嗎?
如果我傳一個自建類 UserModel 進去會記錄出什麼資訊呢?請接下來看 formatter 參數。
exception(可為null)
異常。
不多說了,如果當前上下文有異常,你丟進去就好了。
formatter(不可為null)
格式化器。
這個參數是一個委託可以看到定義“Func<TState,Exception,string>”,這個就可以解釋state是非字元的情況下如何記錄日誌了。
這邊可以通過你自己的邏輯來重建訊息的內容(異常資訊都會進行輸出)。
如果傳入null,日誌組件會使用預設的格式化器替換,預設的格式化器邏輯是調用“state.ToString()”
擴充方法
當然Logging組件為我們提供了大量擴充方法以簡化我們的編碼。
以下是方法存根,參數說明可以對照上文。
EventId效果
日誌域
日誌域可以彙總一類的訊息,非常適合約一種類型不同維度日誌記錄。
日誌過濾器
Logging提供了一個封裝實現用來實現日誌過濾,我們先來看看使用。
可以看到可以通過制定 CategoryName 及最小日誌等級來控制日誌是否輸出,這邊有個有趣的事情。
就是 CategoryName 可以模糊比對。
在 Logging 組件內部擋識別到 CategoryName 為:“ConsoleApp.MyClass”時會把這個分類名稱分割為:
“ConsoleApp.MyClass”
“ConsoleApp”
ps:如果你的命名空間中存在多個“.”符號則還會被分割。
分割完成之後會將這些 Key 拿去與“FilterLoggerSettings”中的字典表進行匹配,優先最大匹配,也就是說如果我們配置了“ConsoleApp.MyClass”這條項目,則優先使用這條,否則繼續尋找“ConsoleApp”這條項目,如果都沒匹配到則使用預設的規則。
實現傳送門:https://github.com/aspnet/Logging/blob/patch/1.1.3/src/Microsoft.Extensions.Logging.Filter/Internal/FilterLogger.cs
在 NLog、log4jnet 等組件中模糊比對是採用“.*”的方式,例如:”ConsoleApp.*”,在 .NET Core 中的 Logging 中是不被支援的(把“.*”去掉實現相同的效果),這點需要注意。
注意
“.WithFilter”是使用封裝的方式進行整合,所以內部會單獨維護一個 FilterLogger,也就意味著所有的 LoggerProvider 必須在 FilterFactory 中進行註冊,不然過濾器是不會生效的哦。以下是錯誤的樣本:
紅色框框部分的兩句應該對調,“.WithFilter”應該優先調用。
特殊的Logger => NullLogger
這個我覺得 .NET Core 是從 Orchard“偷”過來的,Orchard 滿地的 NullLogger.Instance。
為什麼需要 NullLogger?
在業務系統中,Logger 其實並不影響邏輯,換句話說,Logger如果失敗不應該影響業務。
在單元測試時 Logger 也可以忽略。
這句話肯定是對的,但在遍地DI的項目中 Logger 很有可能被開發人員傳入null,這時候就會影響業務的執行,那麼這時候 NullLogger 非常適合做那個最糟糕的實現者。
用來替換日誌記錄或防止“NullReferenceException”這類異常的發生。
非常可惜的是,1.1.3版本中沒有提供 NullLogger<T> 這樣的實現。好訊息是在 .NET Standard2.0 中已經提供了 NullLogger<T> 的實現。
我們下面來看看可使用的情境:
可以看到在沒有添加 Logging 組件的時候日誌記錄也不會拋出異常。
ps:NullLogger<T> 摘抄至.NET Standard2.0中的 NullLoggerOfT.cs。
地址:https://github.com/aspnet/Logging/blob/dev/src/Microsoft.Extensions.Logging.Abstractions/NullLoggerOfT.cs
Logger in DependencyInjection不使用 Filter
使用 Filter
寫在最後
不得不感歎微軟在 .NET Core 中統一了非常多的常用組件,為開發人員統一環境提供了極大的方便。
後續的文章會分享如何整合第三方 Logging 組件,比如:NLog、log4jnet、Exceptionless 等。
.NET技術棧QQ群:384413261(點擊加入 .NET Group)
擁抱.NET Core系列:Logging (1)