我应该将ILogger,ILogger <t>,ILoggerFactory或ILoggerProvider用作库吗?

时间:2018-07-15 04:08:08

标签: c# .net logging .net-core .net-standard

这可能与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,因此无论如何都会失败。

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

10 个答案:

答案 0 :(得分:51)

定义

我们有3个界面:ILoggerILoggerProviderILoggerFactory。让我们看看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,请使用ILoggerFactory。 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>是正确的选择。

  1. 注入ILoggerFactory会强制您的用户将对可变全局记录器工厂的控制权交给您的类库。此外,通过接受ILoggerFactory,您的类现在可以使用CreateLogger方法以任意类别名称写入日志。尽管ILoggerFactory通常可以在DI容器中作为单例使用,但作为用户,我会怀疑为什么任何库都需要使用它。
  2. 虽然方法ILoggerProvider.CreateLogger看起来像它,但它并不打算注入。它与ILoggerFactory.AddProvider一起使用,因此工厂可以创建汇总的ILogger,该汇总将写入从每个注册的提供程序创建的多个ILogger。当您检查the implementation of LoggerFactory.CreateLogger
  3. 时很明显
  4. 接受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相关的包装类。如果不是协变的,那么您肯定会使用ILoggerFactoryILogger。但是ILogger应该被丢弃,因为您可以将日志记录到任何类别,并且会丢失有关日志记录的所有上下文。我认为ILoggerFactory是最好的方法,然后使用CreateLogger<T>()在课堂上创建一个ILogger<T>。这样,您确实拥有一个不错的解决方案,因为作为开发人员,您确实希望使类别与您的类对齐,以直接跳转到代码而不是某些不相关的派生类。 (您可以添加行号。)您还可以让派生类使用基类定义的记录器,还可以从哪里开始寻找源代码?除此之外,我可以想象您可能在同一类中还具有其他带有特殊用途类别(子)名称的ILogger。在这种情况下,ILoggerFactory看起来更干净,没有什么可以阻止您拥有多个ILogger。

我的首选解决方案是注入ILoggerFactory调用CreatLogger<T>,其中T是当前类,并将其分配给private readonly ILogger<T> logger

答案 8 :(得分:1)

编写库时,ILoggerFactoryILoggerFactory<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>();
  }
}

现在您可以:

  • 使用完整的 MS ILogger 界面来完成所有日志记录,根本不在乎是否真的有记录器
  • 如果您需要控制类别,请将通用 CreateLogger<T>() 替换为非通用 CreateLogger("")

对于抱怨:

  • 是的,如果您不关心类别,您可以在构造函数中使用 ILoggerILogger<T>,但我建议这是最通用/通用的方式,它可以为您提供在不影响消费者的情况下提供最多的选择。
  • 消费者仍然可以使用日志工厂的配置或其记录器实现来覆盖类别。
  • 您不一定要通过接受日志工厂来初始化任何东西,这取决于 DI 容器配置/消费者
  • 空记录器在我的书中不算作开销,因为我们使用的是单个实例
  • 消费者可以传入一个 NullLoggerFactory,如果他们愿意
  • 如果你真的矫枉过正,你可以有一个库配置设置(修改构造函数)将启用/禁用库的日志记录(有条件地强制 NullLogger)

答案 9 :(得分:0)

这是补充的,因为很多其他搜索结果都链接到这里。 如果您有一个 ILoggerFactory 并且您需要提供一个 ILogger<Whatever>,那么创建它的方法是:new Logger<Whatever>(myLoggerFactory)