c# - net - microsoft.extensions.logging




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

定义

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

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维护一个记录器列表,并将日志消息写入所有记录器。 查看Logger源代码, 我们可以确认 Logger 有一个 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 =完全限定类型名称T的记录器

在我看来,令文档混淆的是它没有提到注入非泛型 ILogger 任何内容。 在上面的相同示例中,我们正在注入一个非通用的 ITodoRepository ,但它并没有解释为什么我们不为 ILogger 做同样的事情。

Mark Seemann说

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

将工厂注入Controller不是一个好方法,因为Controller不负责初始化Logger(违反SRP)。 同时注入通用 ILogger<T> 会增加不必要的噪音。 请参阅Steven在 Simple Injector博客上发表的文章

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

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

尼古拉的IoC第四定律

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

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

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

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

我见过的一些代码使用了非通用的 ILogger 接口,甚至可能共享同一个记录器的一个实例,有些代码在他们的ctor中使用 ILogger<T> 并期望DI容器支持开放的泛型类型或每个明确的注册以及我的图书馆使用的每个 ILogger<T> 变体。

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

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


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

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

毕竟,如果我们要从这些课程中进行选择,我们没有比 ILogger<T> 更好的选择。

另一种方法是注入包含非泛型 ILogger 其他东西,在这种情况下应该是非泛型 ILogger 。 我们的想法是,通过使用您自己的类包装它,您可以完全控制用户如何配置它。


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

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


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

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

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

loggerFactory.CreateLogger("MyLibrary");

然后日志将如下所示:

MyLibrary:信息:我的信息日志

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

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

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




.net-standard