[英]Should I take ILogger, ILogger<T>, ILoggerFactory or ILoggerProvider for a library?
這可能與將ILogger 或 ILoggerFactory 傳遞給 AspNet Core 中的構造函數有些相關? ,然而這只是關於庫設計,而不是關於使用這些庫的實際應用程序如何實現其日志記錄。
我正在編寫一個將通過 Nuget 安裝的 .net 標准 2.0 庫,並允許使用該庫的人獲取一些調試信息,我依賴Microsoft.Extensions.Logging.Abstractions來允許注入標准化的 Logger。
但是,我看到多個接口,並且網絡上的示例代碼有時使用ILoggerFactory
並在類的 ctor 中創建一個記錄器。 還有ILoggerProvider
看起來像 Factory 的只讀版本,但實現可能會或可能不會實現這兩個接口,所以我必須選擇。 (工廠似乎比提供者更常見)。
我見過的一些代碼使用非泛型ILogger
接口,甚至可能共享同一個記錄器的一個實例,有些代碼在其 ctor 中采用ILogger<T>
並期望 DI 容器支持開放泛型類型或每個顯式注冊以及我的庫使用的每個ILogger<T>
變體。
現在,我確實認為ILogger<T>
是正確的方法,也許是一個不接受該論點而只傳遞 Null Logger 的 ctor。 這樣,如果不需要記錄,則不使用任何記錄。 但是,一些 DI 容器會選擇最大的 ctor,因此無論如何都會失敗。
我很好奇我應該在這里做什么來給用戶帶來最少的頭痛,同時如果需要的話仍然允許適當的日志支持。
我們有 3 個接口: ILogger
、 ILoggerProvider
和ILoggerFactory
。 讓我們看一下源代碼,找出它們的職責:
ILogger :負責寫入給定日志級別的日志消息。
ILoggerProvider :負責創建ILogger
的實例(您不應該直接使用ILoggerProvider
來創建記錄器)
ILoggerFactory :您可以向工廠注冊一個或多個ILoggerProvider
,而工廠又使用所有這些來創建ILogger
的實例。 ILoggerFactory
擁有ILoggerProviders
的集合。
在下面的示例中,我們向工廠注冊了 2 個提供程序(控制台和文件)。 當我們創建一個記錄器時,工廠使用這兩個提供者來創建一個Logger
實例:
ILoggerFactory factory = new LoggerFactory().AddConsole(); // add console provider
factory.AddProvider(new LoggerFileProvider("c:\\log.txt")); // add file provider
Logger logger = factory.CreateLogger(); // creates a console logger and a file logger
因此,記錄器本身正在維護ILogger
的集合,並將日志消息寫入所有這些。 查看 Logger 源代碼我們可以確認Logger
有一個 ILogger 數組(即ILoggers
LoggerInformation[]
),同時它正在實現ILogger
接口。
MS 文檔提供了兩種注入記錄器的方法:
1、注廠:
public TodoController(ITodoRepository todoRepository, ILoggerFactory logger) { _todoRepository = todoRepository; _logger = logger.CreateLogger("TodoApi.Controllers.TodoController"); }
使用 Category = TodoApi.Controllers.TodoController 創建一個 Logger 。
2. 注入一個通用的
ILogger<T>
:public TodoController(ITodoRepository todoRepository, ILogger<TodoController> logger) { _todoRepository = todoRepository; _logger = logger; }
使用 Category = TodoController 的完全限定類型名稱創建一個記錄器
在我看來,使文檔令人困惑的是它沒有提到任何關於注入非泛型ILogger
的內容。 在上面的同一個示例中,我們正在注入一個非泛型ITodoRepository
,但是它並沒有解釋為什么我們沒有對ILogger
做同樣的事情。
根據馬克·西曼的說法:
注入構造函數應該只接收依賴項。
將工廠注入到Controller中並不是一個好方法,因為初始化Logger不是Controller的職責(違反SRP)。 同時注入一個通用的ILogger<T>
會增加不必要的噪音。 有關詳細信息,請參閱Simple Injector 的博客: ASP.NET Core DI 抽象有什么問題?
應該注入(至少根據上面的文章)是一個非通用ILogger
,但是,這不是微軟的內置 DI 容器可以做的事情,您需要使用第 3 方 DI 庫。 這兩個文檔解釋了如何將 3rd 方庫與 .NET Core 一起使用。
這是 Nikola Malovic 的另一篇文章,他在其中解釋了他的 5 條 IoC 定律。
尼古拉的國際奧委會第四定律
正在解析的類的每個構造函數除了接受一組自己的依賴項外,不應有任何實現。
除了ILoggerProvider
之外,這些都是有效的。 ILogger
和ILogger<T>
是您應該用於記錄的。 要獲得ILogger
,請使用ILoggerFactory
。 ILogger<T>
是獲取特定類別的記錄器的快捷方式(類型為類別的快捷方式)。
當您使用ILogger
執行日志記錄時,每個注冊的ILoggerProvider
都有機會處理該日志消息。 使用代碼直接調用ILoggerProvider
並不真正有效。
ILogger<T>
是為 DI 制作的實際版本。 ILogger<T>
的出現是為了幫助更輕松地實現工廠模式,而不是您自己編寫所有 DI 和工廠邏輯,這是 ASP.NET Core 中最明智的決定之一
您可以選擇:
ILogger<T>
如果您需要在代碼中使用工廠模式和 DI 模式,或者您可以使用ILogger
來實現不需要 DI 的簡單日志記錄。
鑒於此, ILoggerProvider
只是處理每個已注冊日志消息的橋梁。 無需使用它,因為它不會影響您應該干預代碼的任何內容。 它偵聽注冊的ILoggerProvider
並處理消息。 就是這樣。
ILoggerFactory
或ILoggerFactory<T>
是要走的路。我以這樣一種方式編寫類,即我控制消息的內容和嚴重性(有時是類別),同時允許消費者選擇他們想要的任何日志記錄實現,或者如果他們願意,則根本不選擇。
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
public class MyClass
{
private readonly ILogger _logger;
public MyClass(
..., /* required deps */
..., /* other optional deps */
ILoggerFactory? loggerFactory)
{
_logger = loggerFactory?.CreateLogger<MyClass>()
?? NullLoggerFactory.Instance.CreateLogger<MyClass>();
}
}
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
public class MyClass<T>
{
private readonly ILogger<T> _logger;
public MyClass<T>(
..., /* required deps */
..., /* other optional deps */
ILoggerFactory? loggerFactory)
{
_logger = loggerFactory?.CreateLogger<T>()
?? NullLoggerFactory.Instance.CreateLogger<T>();
}
}
CreateLogger<T>()
替換非通用CreateLogger("")
。ILogger
或ILogger<T>
,但我建議這是最通用/通用的方式,它為您提供了最多的選擇,而不會影響消費者.對於圖書館設計,好的方法是:
不要強迫消費者將記錄器注入你的類。 只需創建另一個傳遞 NullLoggerFactory 的構造函數。
class MyClass { private readonly ILoggerFactory _loggerFactory; public MyClass():this(NullLoggerFactory.Instance) { } public MyClass(ILoggerFactory loggerFactory) { this._loggerFactory = loggerFactory ?? NullLoggerFactory.Instance; } }
限制創建記錄器時使用的類別數量,以允許消費者輕松配置日志過濾。
this._loggerFactory.CreateLogger(Consts.CategoryName)
堅持這個問題,我認為ILogger<T>
是正確的選擇,考慮到其他選項的缺點:
ILoggerFactory
強制您的用戶將可變全局記錄器工廠的控制權交給您的類庫。 此外,通過接受ILoggerFactory
,您的類現在可以使用CreateLogger
方法使用任意類別名稱寫入日志。 雖然ILoggerFactory
通常可用作 DI 容器中的單例,但作為用戶,我會懷疑為什么任何庫都需要使用它。ILoggerProvider.CreateLogger
方法看起來很像,但它並非用於注入。 它與ILoggerFactory.AddProvider
使用,因此工廠可以創建聚合ILogger
,以寫入從每個注冊的提供程序創建的多個ILogger
。 當您檢查LoggerFactory.CreateLogger
的實現時,這一點很清楚ILogger
看起來也是可行的方法,但使用 .NET Core DI 是不可能的。 這實際上聽起來像是他們首先需要提供ILogger<T>
的原因。 所以畢竟,如果我們要從這些類中進行選擇,我們沒有比ILogger<T>
更好的選擇。
另一種方法是注入其他包裝非泛型ILogger
的東西,在這種情況下應該是非泛型的。 這個想法是,通過用您自己的類包裝它,您可以完全控制用戶如何配置它。
默認方法是ILogger<T>
。 這意味着在日志中來自特定類的日志將清晰可見,因為它們將包含完整的類名作為上下文。 例如,如果您的類的全名是MyLibrary.MyClass
,您將在此類創建的日志條目中得到它。 例如:
MyLibrary.MyClass:Information:我的信息日志
如果要指定自己的上下文,則應使用ILoggerFactory
。 例如,您庫中的所有日志都具有相同的日志上下文,而不是每個類。 例如:
loggerFactory.CreateLogger("MyLibrary");
然后日志將如下所示:
MyLibrary:Information: 我的信息日志
如果您在所有類中都這樣做,那么上下文將只是所有類的 MyLibrary。 如果您不想在日志中公開內部類結構,我想您會希望為庫執行此操作。
關於可選的日志記錄。 我認為您應該始終在構造函數中要求 ILogger 或 ILoggerFactory 並將其留給庫的使用者將其關閉或提供一個在依賴注入中不執行任何操作的 Logger,如果他們不想記錄的話。 在配置中為特定上下文關閉日志記錄非常容易。 例如:
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"MyLibrary": "None"
}
}
}
我寧願保持簡單並注入非通用 ILogger
這似乎是非默認行為 - 但很容易與以下內容聯系起來:
services.AddTransient(s => s.GetRequiredService<ILoggerFactory>().CreateLogger(""));
這(將 ILogger 注入構造函數並調用需要 ILogger 的基類)是唯一可能的,因為ILogger<T>
是協變的,並且只是一個依賴於LoggerFactory
的包裝類。 如果它不是協變的,您肯定會使用ILoggerFactory
或ILogger
。 但是ILogger
應該被丟棄,因為您可以記錄到任何類別,並且您會丟失有關記錄的所有上下文。 我認為ILoggerFactory
將是最好的方法,然后使用CreateLogger<T>()
在您的班級中創建ILogger<T>
。 這樣你真的有一個很好的解決方案,因為作為開發人員,你真的希望將類別與你的類對齊,以便直接跳轉到代碼而不是一些不相關的派生類。 (您可以添加行號。)您還可以讓您的派生類使用由基類定義的記錄器,但也可以從哪里開始查找源代碼? 除此之外,我可以想象您在同一類中可能還有任何其他具有特殊用途類別(子)名稱的ILogger
。 在這種情況下,沒有什么能阻止您擁有多個 ILogger, ILoggerFactory
看起來更干凈。
我的首選解決方案是注入ILoggerFactory
調用CreatLogger<T>
其中T
是當前類並將其分配給private readonly ILogger<T> logger
或者,如果您已經注入IServiceProvider
,您可以調用serviceProvider.GetService<ILogger<T>>();
請注意,注入IServiceProvider
是服務定位器模式,被視為反模式。 注入ILoggerFactory
也是服務定位器模式的一種變體。
注意:服務提供者緩存記錄器工廠沒有的記錄器。 如果不希望使用服務提供者,可以注入記錄器管理器:
public interface ILoggerManager
{
public ILogger<T> CreateLogger<T>();
}
public class LoggerManager : ILoggerManager
{
private readonly IServiceProvider _serviceProvider;
public LoggerManager(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public ILogger<T> CreateLogger<T>()
{
return _serviceProvider.GetRequiredService<ILogger<T>>();
}
}
這增加了,因為很多其他的搜索結果鏈接在這里。 如果您有ILoggerFactory
並且需要提供ILogger<Whatever>
,這是創建它的方法: new Logger<Whatever>(myLoggerFactory)
我已經使用這種簡單的技術將 Ilogger 注入到需要基本 ILogger 的遺留類中
services.AddSingleton<ILogger>(provider => provider.GetService<ILogger<Startup>>());
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.