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

Mic*_*tum 93 .net c# logging .net-core .net-standard

这可能与Passog ILogger或ILoggerFactory与AspNet Core中的构造函数有些关联但是,这特别是关于库设计,而不是关于使用这些库的实际应用程序如何实现其日志记录.

我写一个.NET 2.0标准库将通过的NuGet安装,并允许使用图书馆的人得到一些调试信息,我根据Microsoft.Extensions.Logging.Abstractions允许标准化的记录器被注入.

但是,我看到了多个接口,Web上的示例代码有时会使用ILoggerFactory并在类的ctor中创建一个记录器.还有ILoggerProvider看起来像Factory的只读版本,但实现可能会也可能不会实现两个接口,所以我必须选择.(工厂似乎比提供商更常见).

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

现在,我确实认为这ILogger<T>是正确的方法,也许是一个不接受该论证而只是通过Null Logger的ctor.这样,如果不需要记录,则不使用任何记录.然而,一些DI容器选择了最大的ctor,因此无论如何都会失败.

我很好奇我应该在这里做什么来为用户创造最少的头痛,同时如果需要仍然允许适当的日志记录支持.

Hoo*_*ini 82

定义

我们有3个接口:ILogger,ILoggerProviderILoggerFactory.让我们看看源代码,找出他们的职责:

ILogger:主要职责是编写给定日志级别的日志消息.

ILoggerProvider:只有一个责任,那就是创造一个ILoggerProvider.

ILogger:有2个职责,创建一个ILoggerFactory并添加一个 ILoggerProviders.

请注意,我们可以向工厂注册一个或多个提供程序(控制台,文件等).当我们使用工厂创建记录器时,它使用已注册的提供程序来创建相应的记录器.

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
Run Code Online (Sandbox Code Playgroud)

因此Logger维护一个记录器列表,并将日志消息写入所有记录器.查看Logger源代码,我们可以确认它ILogger有一个数组Logger(即LoggerInformation []),同时它正在实现ILoggers接口.


依赖注入

MS文档提供了两种注入记录器的方法:

1.注射工厂:

public TodoController(ITodoRepository todoRepository, ILoggerFactory logger)
{
    _todoRepository = todoRepository;
    _logger = logger.CreateLogger("TodoApi.Controllers.TodoController");
}
Run Code Online (Sandbox Code Playgroud)

使用Category = TodoApi.Controllers.TodoController创建一个Logger.

2.注入通用LoggerInformation[]:

public TodoController(ITodoRepository todoRepository, ILogger<TodoController> logger)
{
    _todoRepository = todoRepository;
    _logger = logger;
}
Run Code Online (Sandbox Code Playgroud)

创建一个具有Category =完全限定类型名称T的记录器


在我看来,令文档混淆的是它没有提到注入非泛型的任何内容ILogger.在上面的相同示例中,我们注入了非泛型ILogger<T>,但它并没有解释为什么我们不会这样做ILogger.

马克·西曼:

注入构造函数应该只接收依赖项.

将工厂注入Controller并不是一个好方法,因为Controller不负责初始化Logger(违反SRP).同时注入通用ITodoRepository会增加不必要的噪音.请参阅Steven在Simple Injector博客上的帖子

什么应该注入(至少根据上面的文章)是非通用的ILogger,但是,这不是微软的内置DI容器可以做的事情,你需要使用第三方DI库.

这是尼古拉·马洛维奇的另一篇文章,他解释了他对IoC的5条法则.

尼古拉的IoC第四定律

除了接受一组自己的依赖项之外,正在解析的类的每个构造函数都不应该有任何实现.

  • 你的回答和史蒂文的文章是最正确的,同时也是最令人沮丧的。 (7认同)
  • 如果未找到“AddConsole”,请确保已安装 NuGet“Microsoft.Extensions.Logging.Console”,源文件包含“using Microsoft.Extensions.Logging;”。然后最后尝试`ILoggerFactoryfactory = LoggerFactory.Create(builder =&gt; builder.AddConsole());` (2认同)
  • 抱歉,您声称哪篇文章(特别是文本的哪一部分)解释了为什么注入 ILogger&lt;T&gt; 是一件坏事?我同意一般不注入工厂,但为什么 ILogger&lt;T&gt; 比 ILogger 更糟糕?T 通常是接受依赖的消费类 (2认同)
  • 引用史蒂文对[这个问题]的评论(/sf/ask/3279017731/):“注入`ILogger&lt;T &gt;` 对消费者来说只是噪音,当指定了错误的 T 时,可能会导致意外错误,并使测试变得复杂。"..."注入 `ILoggerFactory` 和 `ILogger&lt;T&gt;` 是一个糟糕的主意,正如我所见微软这样做(并公开推广)的唯一原因是他们的内置容器缺乏将非通用接口映射到通用实现的可能性。” (2认同)

dav*_*owl 26

除了ILoggerProvider之外,这些都是有效的.ILogger和ILogger <T>是你应该用于记录的东西.要获得ILogger,请使用ILoggerFactory.ILogger <T>是获取特定类别的记录器的快捷方式(类型的快捷方式作为类别).

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

  • @MichaelStum - 我不确定我在这里遵循你的逻辑.您期望您的消费者使用DI系统,但是又想要接管并手动管理库中的依赖关系?为什么这似乎是正确的方法? (3认同)

小智 20

当编写一个库时,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>();
  }
}
Run Code Online (Sandbox Code Playgroud)

通用类

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>();
  }
}
Run Code Online (Sandbox Code Playgroud)

现在你可以:

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

对于抱怨:

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


Bar*_*r J 8

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

你可以选择:

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

鉴于此,ILoggerProvider它只是处理每个已注册日志消息的桥梁.没有必要使用它,因为它不会影响您应该干预代码的任何内容.它会监听已注册的ILoggerProvider并处理消息.就是这样.

  • ILogger 与 ILogger&lt;T&gt; 与 DI 有什么关系?要么会被注射,不是吗? (2认同)

Ale*_*esD 8

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

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

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

loggerFactory.CreateLogger("MyLibrary");
Run Code Online (Sandbox Code Playgroud)

然后日志将如下所示:

MyLibrary:Information: 我的信息日志

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

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

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "MyLibrary": "None"
    }
  }
}
Run Code Online (Sandbox Code Playgroud)


tia*_*tia 6

坚持这个问题,我认为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在这种情况下应该是非泛型的.我们的想法是,通过使用您自己的类包装它,您可以完全控制用户如何配置它.


And*_*rry 6

我更愿意保持简单并注入非通用 ILogger

这似乎是非默认行为 - 但很容易与以下内容连接:

services.AddTransient(s => s.GetRequiredService<ILoggerFactory>().CreateLogger(""));
Run Code Online (Sandbox Code Playgroud)

  • 因为也许有人不关心这样的功能。花了13年时间,没有按类别过滤。此外,更高级的 DI 容器能够以上下文感知的方式解析“ILogger”,以在运行时获取“ILogger&lt;[Class-Where-I'm-Injected]&gt;”,这是我最喜欢的多次重复这个不太好的“自我通用”bolierplate。当然这只是我的意见。 (2认同)

Iha*_*ush 5

对于图书馆设计,好的方法是:

1.不要强迫消费者向你的课程注入记录器.只需创建另一个传递NullLoggerFactory的ctor.

class MyClass
{
    private readonly ILoggerFactory _loggerFactory;

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

    }
    public MyClass(ILoggerFactory loggerFactory)
    {
      this._loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
    }
}
Run Code Online (Sandbox Code Playgroud)

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