我应该将ILogger,ILogger <T>,ILoggerFactory或ILoggerProvider用于库吗?


113

这可能与将ILogger或ILoggerFactory传递给AspNet Core中的构造函数有关吗?,但这是专门针对库设计的,而不是有关使用这些库的实际应用程序如何实现其日志记录的。

我正在编写一个将通过Nuget安装的.net Standard 2.0库,并且为了允许使用该库的人们获得一些调试信息,我依赖于Microsoft.Extensions.Logging.Abstractions来允许注入标准化的Logger。

但是,我看到了多个接口,并且网络上的示例代码有时ILoggerFactory会在类的ctor中使用并创建一个记录器。还有ILoggerProvider一个看起来像是Factory的只读版本,但是实现可能会也可能不会实现这两个接口,因此我不得不选择。(工厂似乎比提供程序更常见)。

我见过的某些代码使用非泛型ILogger接口,甚至可能共享同一记录器的一个实例,而某些代码采用了一个ILogger<T>ctor,并期望DI容器支持开放的泛型类型或ILogger<T>我的库中每个变体的显式注册。用途。

现在,我确实认为这ILogger<T>是正确的方法,也许是不采用该参数而仅通过Null Logger的ctor。这样,如果不需要日志记录,则不使用任何日志记录。但是,某些DI容器会选择最大的ctor,因此无论如何都会失败。

我很好奇我在这里应该做的事情,以便为用户带来最少的麻烦,同时在需要时仍允许适当的日志记录支持。

Answers:


113

定义

我们有3个接口:ILoggerILoggerProviderILoggerFactory。让我们看一下源代码以找出他们的职责:

ILogger:负责编写给定日志级别的日志消息。

ILoggerProvider:负责创建的实例ILogger(不应ILoggerProvider直接使用它来创建记录器)

ILoggerFactory:您可以ILoggerProvider在工厂注册一个或多个,然后依次使用它们全部创建的实例ILoggerILoggerFactory持有的收藏ILoggerProviders

在下面的示例中,我们正在工厂注册2个提供程序(控制台和文件)。创建记录器时,工厂将使用这两个提供程序来创建记录器实例:

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

因此,记录器本身将维护ILoggers 的集合,并将日志消息写入所有记录。查看Logger源代码,我们可以确认它Logger具有ILoggersLoggerInformation[])数组,同时正在实现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

根据马克·西曼Mark Seemann)的说法:

注入构造函数只需要接收依赖项即可。

将工厂注入Controller并不是一个好方法,因为初始化Logger(违反SRP)不是Controller的责任。同时注入泛型ILogger<T>会增加不必要的噪音。有关更多详细信息,请参见Simple Injector的博客:ASP.NET Core DI抽象有什么问题?

(至少根据上面的文章)应该注入的是非泛型的ILogger,但是,那不是Microsoft的内置DI容器可以做到的,您需要使用第三方的DI库。 两个文档说明了如何在.NET Core中使用第三方库。


这是Nikola Malovic 撰写另一篇文章,他在其中解释了他的IoC的5条定律。

尼古拉的IoC第四定律

被解析类的每个构造函数除了接受一组自己的依赖关系之外,都不应具有任何实现。


3
您的答案和史蒂文的文章同时是最正确和最令人沮丧的。
bokibeg

30

除以外,所有这些均有效ILoggerProviderILogger以及ILogger<T>您应该用于记录的内容。要获取ILogger,请使用ILoggerFactoryILogger<T>是获取特定类别记录器的快捷方式(该类型作为类别的快捷方式)。

当您使用ILogger进行日志记录时,每个注册的人ILoggerProvider都有机会处理该日志消息。消费代码ILoggerProvider直接调用它并不是真的有效。


谢谢。这使我认为,ILoggerFactory通过仅具有1个依赖关系(“只要给我一个工厂,然后我将创建自己的记录器”),为消费者连接DI的简便方法就是一个很好的方法,但是会阻止使用现有的记录器(除非消费者使用一些包装材料)。不管是否使用ILogger通用工具,消费者都可以给我他们专门准备的记录器,但是DI设置可能会变得更加复杂(除非使用支持Open Generics的DI容器)。听起来正确吗?那样的话,我想我去工厂。
Michael Stum

3
@MichaelStum-我不确定在这里是否遵循您的逻辑。您希望您的使用者使用DI系统,但随后想要接管并手动管理库中的依赖项吗?为什么这似乎是正确的方法?
Damien_The_Unbeliever

@Damien_The_Unbeliever很好。拿工厂似乎很奇怪。我想,而不是采取一个ILogger<T>,我会采取ILogger<MyClass>或者甚至只是ILogger代替-这样一来,用户可以只用一个单一的注册连线起来,没有在他们的DI容器需要开放的仿制药。倾向于非泛型ILogger,但周末会进行很多实验。
Michael Stum

10

ILogger<T>是为DI制作的实际产品。ILogger的出现是为了帮助您更轻松地实现工厂模式,而不是您自己编写所有的DI和Factory逻辑,这是asp.net核心中最明智的决定之一。

您可以选择:

ILogger<T>如果您需要在代码中使用工厂和DI模式,或者可以使用ILogger来实现不需要DI的简单日志记录。

鉴于此,The ILoggerProvider只是处理每个注册日志消息的桥梁。不需要使用它,因为它不会影响您应干预的任何代码,它会侦听已注册的ILoggerProvider并处理消息。就是这样


1
ILogger vs ILogger <T>与DI有什么关系?要么被注射,不是吗?
Matthew Hostetler

它实际上是MS Docs中的ILogger <TCategoryName>。它派生自ILogger,除了要记录的类名之外,没有添加任何新功能。这也提供了唯一的类型,因此DI注入正确命名的记录器。Microsoft扩展扩展了非泛型this ILogger
塞缪尔·丹尼尔森

8

ILogger<T>考虑到其他选择的缺点,坚持这个问题,我认为这是正确的选择:

  1. 注入ILoggerFactory迫使您的用户将对可变全局记录器工厂的控制权交给了类库。此外,通过接受ILoggerFactory您的类,现在可以使用CreateLogger方法使用任意类别名称写入日志。虽然ILoggerFactory通常可以在DI容器中作为单例使用,但作为用户,我会怀疑为什么任何库都需要使用它。
  2. 虽然该方法ILoggerProvider.CreateLogger看起来像它,但它并不适合注射。它与一起使用,ILoggerFactory.AddProvider以便工厂可以创建聚合ILogger,该聚合将写入ILogger从每个注册的提供程序创建的多个副本。这是明显的,当你检查的实施LoggerFactory.CreateLogger
  3. 接受ILogger看起来也很可行,但是.NET Core DI是不可能的。实际上,这听起来像是他们需要首先提供的原因ILogger<T>

因此,毕竟,ILogger<T>如果要从这些类中进行选择,我们别无选择。

另一种方法是注入包装非泛型的其他内容,ILogger在这种情况下,这种情况应该是非泛型的。这个想法是通过将它包装到您自己的类中,您可以完全控制用户如何配置它。


6

默认方法是ILogger<T>。这意味着在日志中,特定类的日志将清晰可见,因为它们将包含完整的类名作为上下文。例如,如果您的类的全名是MyLibrary.MyClass您将在该类创建的日志条目中获得此名称。例如:

MyLibrary.MyClass:Information:我的信息日志

ILoggerFactory如果要指定自己的上下文,则应使用。例如,库中的所有日志都具有相同的日志上下文,而不是每个类。例如:

loggerFactory.CreateLogger("MyLibrary");

然后日志将如下所示:

MyLibrary:信息:我的信息日志

如果在所有类中都这样做,那么上下文将只是所有类的MyLibrary。我想如果您不想在日志中公开内部类结构,那么您将为库执行此操作。

关于可选的日志记录。我认为您应该始终在构造函数中要求ILogger或ILoggerFactory,并将其留给库的使用者以将其关闭,或者提供一个Logger,如果他们不想记录,则在依赖项注入中不执行任何操作。为配置中的特定上下文打开日志记录非常容易。例如:

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "MyLibrary": "None"
    }
  }
}

5

对于库设计,好的方法是:

1.请勿强迫消费者将记录器注入您的课程。只需创建另一个传递NullLoggerFactory的ctor。

class MyClass
{
    private readonly ILoggerFactory _loggerFactory;

    public MyClass():this(NullLoggerFactory.Instance)
    {

    }
    public MyClass(ILoggerFactory loggerFactory)
    {
      this._loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
    }
}

2.限制创建记录器时使用的类别数,以允许使用者轻松配置日志过滤。 this._loggerFactory.CreateLogger(Consts.CategoryName)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.