这可能与Pass ILogger or ILoggerFactory to constructors in AspNet Core?有点相关,但这与库设计有关,而不是与使用这些库的实际应用程序如何实现其日志记录有关。
我正在编写一个将通过Nuget安装的.net Standard 2.0库,并且为了允许使用该库的人们获取一些调试信息,我依靠Microsoft.Extensions.Logging.Abstractions来允许注入标准化的Logger
但是,我看到了多个接口,并且网络上的示例代码有时会使用dataFilter <- function(data){
rawData %>% group_by(date) %>%
summarise(paste(data,"Count" = n(data)))
}
Data <- dataFilter(Income)
并在类的ctor中创建一个记录器。还有ILoggerFactory
看起来像是Factory的只读版本,但是实现可能会也可能不会实现这两个接口,因此我必须选择。 (工厂似乎比提供程序更常见。)
我见过的某些代码使用非通用的ILoggerProvider
接口,甚至可能共享同一记录器的一个实例,有些代码在其ctor中带有一个ILogger
并期望DI容器能够支持开放通用类型或我的库使用的每个ILogger<T>
变体的显式注册。
现在,我确实认为ILogger<T>
是正确的方法,也许是不采用该参数而仅通过Null Logger的ctor。这样,如果不需要日志记录,则不使用任何日志记录。但是,某些DI容器会选择最大的ctor,因此无论如何都会失败。
我很好奇在这里应该做的事情,以便为用户带来最少的麻烦,同时在需要时仍允许适当的日志记录支持。
答案 0 :(得分:51)
我们有3个界面:ILogger
,ILoggerProvider
和ILoggerFactory
。让我们看看source code来找出他们的职责:
ILogger
:主要职责是编写给定日志级别的日志消息。
ILoggerProvider
:只有一项职责,那就是创建ILogger
。
ILoggerFactory
:具有2个职责,即创建ILogger
并添加一个
ILoggerProvider
。
请注意,我们可以向工厂注册一个或多个提供程序(控制台,文件等)。当我们使用工厂创建记录器时,它将使用所有注册的提供程序来创建其相应的记录器。
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
因此Logger维护着一个记录器列表,并将记录消息写入所有记录器中。 Looking at Logger source code我们可以确认Logger
有一个ILoggers
的数组(即LoggerInformation []),同时它正在实现ILogger
接口。
MS documentation提供了两种注入记录器的方法:
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 =完全限定的类型名称T创建一个记录器
我认为,使文档令人困惑的是,它没有提及有关注入非泛型ILogger
的任何内容。在上面的同一示例中,我们注入了一个非泛型的ITodoRepository
,但是并没有解释为什么我们不对ILogger
做同样的事情。
根据Mark Seemann:
Injection构造函数只应接受 依赖性。
将工厂注入Controller并不是一个好方法,因为初始化Logger(违反SRP)不是Controller的责任。同时注入通用ILogger<T>
会增加不必要的噪音。请参阅史蒂文在Simple Injector blog
(至少根据上面的文章)应该注入的是非泛型ILogger
,但是,那不是Microsoft的内置DI容器可以做到的,您需要使用第三个派对DI库。
这是Nikola Malovic的another article,他在其中解释了他的IoC的5条定律。
尼古拉的IoC第四定律
要解析的类的每个构造函数都不应具有任何 实现,而不是接受一组自己的依赖项。
答案 1 :(得分:23)
除ILoggerProvider以外,其他所有有效。 ILogger和ILogger
使用ILogger执行日志记录时,每个已注册的ILoggerProviders都有机会处理该日志消息。消费代码直接调用ILoggerProvider并不是真的有效。
答案 2 :(得分:7)
ILogger<T>
是为DI制作的实际的。 ILogger的出现是为了帮助您更轻松地实现工厂模式,而不是您自己编写所有DI和Factory逻辑,这是asp.net核心中最明智的决定之一。
您可以选择:
ILogger<T>
,如果需要在代码或中使用工厂和DI模式,则可以使用ILogger
来实现不需要DI的简单日志记录。
鉴于ILoggerProvider
只是处理每个已注册日志消息的桥梁。不需要使用它,因为它不会影响您应干预的任何代码,它会侦听已注册的ILoggerProvider并处理消息。就是这样
答案 3 :(得分:5)
对于库设计,好的方法是:
1。请勿强迫使用者将记录器注入您的班级。只需通过NullLoggerFactory创建另一个ctor。
/usr/local/bin/npm -> ../lib/node_modules/npm/bin/npm-cli.js
2。限制创建记录器时使用的类别数,以允许使用者轻松配置日志过滤。
> node -v
v8.0.0
> npm -v
bash: npm: command not found
答案 4 :(得分:5)
考虑到其他选择的缺点,我相信ILogger<T>
是正确的选择。
ILoggerFactory
会强制您的用户将对可变全局记录器工厂的控制权交给您的类库。此外,通过接受ILoggerFactory
,您的类现在可以使用CreateLogger
方法以任意类别名称写入日志。尽管ILoggerFactory
通常可以在DI容器中作为单例使用,但作为用户,我会怀疑为什么任何库都需要使用它。ILoggerProvider.CreateLogger
看起来像它,但它并不打算注入。它与ILoggerFactory.AddProvider
一起使用,因此工厂可以创建汇总的ILogger
,该汇总将写入从每个注册的提供程序创建的多个ILogger
。当您检查the implementation of LoggerFactory.CreateLogger
ILogger
看起来也很可行,但是使用.NET Core DI是不可能的。实际上,这听起来像是他们首先需要提供ILogger<T>
的原因。因此,毕竟,如果要从这些类中进行选择,我们没有比ILogger<T>
更好的选择了。
另一种方法是注入包装非通用ILogger
的其他内容,在这种情况下,该cd
应该是非通用的。这个想法是通过将它包装到您自己的类中,您可以完全控制用户如何配置它。
答案 5 :(得分:3)
默认方法为ILogger<T>
。这意味着在日志中,特定类的日志将清晰可见,因为它们将包含完整的类名作为上下文。例如,如果您的班级全名是MyLibrary.MyClass
,您将在该班级创建的日志条目中获得此名称。例如:
MyLibrary.MyClass:Information:我的信息日志
如果要指定自己的上下文,则应使用ILoggerFactory
。例如,库中的所有日志都具有相同的日志上下文,而不是每个类。例如:
loggerFactory.CreateLogger("MyLibrary");
然后日志将如下所示:
MyLibrary:信息:我的信息日志
如果您在所有类中都这样做,则上下文将只是所有类的MyLibrary。我想如果您不想在日志中公开内部类结构,那么您将为库执行此操作。
关于可选日志记录。我认为您应该始终在构造函数中要求ILogger或ILoggerFactory,并将其留给库的使用者以将其关闭,或者提供一个Logger,如果他们不想记录,则在依赖项注入中不执行任何操作。为配置中的特定上下文打开日志记录非常容易。例如:
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"MyLibrary": "None"
}
}
}
答案 6 :(得分:1)
我希望保持简单,并注入非通用的ILogger
这似乎是非默认行为-但可以通过以下方式轻松连接起来:
services.AddTransient(s => s.GetRequiredService<ILoggerFactory>().CreateLogger(""));
答案 7 :(得分:1)
这(将ILogger注入到构造函数中并调用需要ILogger的基础)是唯一可行的,因为ILogger<T>
是协变的,并且只是一个与LoggerFactory
相关的包装类。如果不是协变的,那么您肯定会使用ILoggerFactory
或ILogger
。但是ILogger
应该被丢弃,因为您可以将日志记录到任何类别,并且会丢失有关日志记录的所有上下文。我认为ILoggerFactory
是最好的方法,然后使用CreateLogger<T>()
在课堂上创建一个ILogger<T>
。这样,您确实拥有一个不错的解决方案,因为作为开发人员,您确实希望使类别与您的类对齐,以直接跳转到代码而不是某些不相关的派生类。 (您可以添加行号。)您还可以让派生类使用基类定义的记录器,还可以从哪里开始寻找源代码?除此之外,我可以想象您可能在同一类中还具有其他带有特殊用途类别(子)名称的ILogger
。在这种情况下,ILoggerFactory
看起来更干净,没有什么可以阻止您拥有多个ILogger。
我的首选解决方案是注入ILoggerFactory
调用CreatLogger<T>
,其中T
是当前类,并将其分配给private readonly ILogger<T> logger
答案 8 :(得分:1)
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>
,但我建议这是最通用/通用的方式,它可以为您提供在不影响消费者的情况下提供最多的选择。答案 9 :(得分:0)
这是补充的,因为很多其他搜索结果都链接到这里。
如果您有一个 ILoggerFactory
并且您需要提供一个 ILogger<Whatever>
,那么创建它的方法是:new Logger<Whatever>(myLoggerFactory)