我应该服用ILogger,ILogger ,ILoggerFactory或ILoggerProvider的库?

问题描述 投票:66回答:6

这可能与Pass ILogger or ILoggerFactory to constructors in AspNet Core?有些相关,但这特别是关于库设计,而不是关于使用这些库的实际应用程序如何实现其日志记录。

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

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

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

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

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

c# .net logging .net-core .net-standard
6个回答
55
投票

定义

我们有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说:

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

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

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

这是Nikola Malovic的another article,他解释了他的IoC法则。

尼古拉的IoC第四定律

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


23
投票

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

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


7
投票

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

你可以选择:

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

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


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)


5
投票

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

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

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

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


3
投票

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

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

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

loggerFactory.CreateLogger("MyLibrary");

然后日志将如下所示:

MyLibrary:信息:我的信息日志

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

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

{
  "Logging": {
    "LogLevel": {
      "Default": "Warning",
      "MyLibrary": "None"
    }
  }
}
© www.soinside.com 2019 - 2024. All rights reserved.