我应该使用 ILogger、ILogger<T>、ILoggerFactory 或 ILoggerProvider 作为库吗? [英] Should I take ILogger, ILogger&lt;T&gt;, ILoggerFactory or ILoggerProvider for a library?

查看:28
本文介绍了我应该使用 ILogger、ILogger<T>、ILoggerFactory 或 ILoggerProvider 作为库吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

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

现在,我确实认为 ILogger<T> 是正确的方法,而且可能是一个不接受该参数而仅传递 Null Logger 的 ctor.这样,如果不需要日志记录,则不使用任何日志记录.然而,一些 DI 容器会选择最大的 ctor,因此无论如何都会失败.

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

解决方案

定义

我们有 3 个接口:ILoggerILoggerProviderILoggerFactory.让我们看看源代码来找出答案他们的职责:

ILogger:负责写入给定日志级别的日志消息.

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

ILoggerFactory:您可以向工厂注册一个或多个 ILoggerProvider ,工厂依次使用它们来创建 ILogger.ILoggerFactory 持有 ILoggerProviders 的集合.

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

ILoggerFactory factory = new LoggerFactory().AddConsole();//添加控制台提供者factory.AddProvider(new LoggerFileProvider("c:\log.txt"));//添加文件提供者Logger logger = factory.CreateLogger();//<-- 创建一个控制台记录器和一个文件记录器

因此记录器本身正在维护一个 ILogger 的集合,并将日志消息写入所有这些.查看Logger源代码我们可以确认Logger有一个ILoggers的数组(即LoggerInformation[]),同时它正在实现ILogger界面.


依赖注入

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

<块引用>

1.注入工厂:

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

创建一个 Category = TodoApi.Controllers.TodoController 的 Logger.

<块引用>

2.注入通用的ILogger:

public TodoController(ITodoRepository todoRepository, ILogger logger){_todoRepository = todoRepository;_logger = 记录器;}

使用 Category = TodoController 的完全限定类型名称创建一个记录器


在我看来,文档令人困惑的地方在于它没有提到任何关于注入非泛型 ILogger 的内容.在上面的同一个例子中,我们注入了一个非通用的 ITodoRepository,然而,它并没有解释为什么我们不对 ILogger 做同样的事情.

根据马克西曼:

<块引用>

一个注入构造函数应该做的只是接收依赖.

将工厂注入控制器不是一个好方法,因为初始化记录器不是控制器的责任(违反 SRP).同时注入通用的 ILogger 会增加不必要的噪音.有关详细信息,请参阅Simple Injector 博客:ASP.NET Core DI 抽象有什么问题?

应该注入的(至少根据上面的文章)是一个非通用的ILogger,但是,这不是微软的内置DI容器可以做到的,你需要使用第 3 方 DI 库.这些 两个文档解释了如何在 .NET 中使用 3rd 方库核心.


这是另一篇文章 Nikola Malovic 在其中解释了他的 IoC 5 定律.

<块引用>

尼古拉第四定律

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

This may be somewhat related to Pass ILogger or ILoggerFactory to constructors in AspNet Core?, however this is specifically about Library Design, not about how the actual application that uses those libraries implement its logging.

I am writing a .net Standard 2.0 Library that will be installed via Nuget, and to allow people using that Library to get some debug info, I'm depending on Microsoft.Extensions.Logging.Abstractions to allow a standardized Logger to be injected.

However, I'm seeing multiple interfaces, and sample code on the web sometimes uses ILoggerFactory and creates a logger in the ctor of the class. There's also ILoggerProvider which looks like a read-only version of the Factory, but implementations may or may not implement both interfaces, so I'd have to pick. (Factory seems more common than Provider).

Some code I've seen uses the non-generic ILogger interface and might even share one instance of the same logger, and some take an ILogger<T> in their ctor and expect the DI container to support open generic types or explicit registration of each and every ILogger<T> variation my library uses.

Right now, I do think that ILogger<T> is the right approach, and maybe a ctor that doesn't take that argument and just passes a Null Logger instead. That way, if no logging is needed, none is used. However, some DI containers pick the largest ctor and thus would fail anyway.

I'm curious of what I'm supposed to be doing here to create the least amount of headache for users, while still allowing proper logging support if desired.

解决方案

Definition

We have 3 interfaces: ILogger, ILoggerProvider and ILoggerFactory. Let's look at the source code to find out their responsibilities:

ILogger: is responsible to write a log message of a given Log Level.

ILoggerProvider: is responsible to create an instance of ILogger (you are not supposed to use ILoggerProvider directly to create a logger)

ILoggerFactory: you can register one or more ILoggerProviders with the factory, which in turn uses all of them to create an instance of ILogger. ILoggerFactory holds a collection of ILoggerProviders.

In the example below, we are registering 2 providers (console and file) with the factory. When we create a logger, the factory uses both of these providers to create an instance of Logger:

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

So the logger itself, is maintaining a collection of ILoggers, and it writes the log message to all of them. Looking at Logger source code we can confirm that Logger has an array of ILoggers (i.e. LoggerInformation[]), and at the same time it is implementing ILogger interface.


Dependency Injection

MS documentation provides 2 methods for injecting a logger:

1. Injecting the factory:

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

creates a Logger with Category = TodoApi.Controllers.TodoController.

2. Injecting a generic ILogger<T>:

public TodoController(ITodoRepository todoRepository, ILogger<TodoController> logger)
{
    _todoRepository = todoRepository;
    _logger = logger;
}

creates a logger with Category = fully qualified type name of TodoController


In my opinion, what makes the documentation confusing is that it does not mention anything about injecting a non-generic, ILogger. In the same example above, we are injecting a non-generic ITodoRepository and yet, it does not explain why we are not doing the same for ILogger.

According to Mark Seemann:

An Injection Constructor should do no more than receiving the dependencies.

Injecting a factory into the Controller is not a good approach, because it is not Controller's responsibility to initialize the Logger (violation of SRP). At the same time injecting a generic ILogger<T> adds unnecessary noise. See Simple Injector's blog for more details: What’s wrong with the ASP.NET Core DI abstraction?

What should be injected (at least according to the article above) is a non-generic ILogger, but then, that's not something that Microsoft's Built-in DI Container can do, and you need to use a 3rd party DI Library. These two documents explain how you can use 3rd party libraries with .NET Core.


This is another article by Nikola Malovic, in which he explains his 5 laws of IoC.

Nikola’s 4th law of IoC

Every constructor of a class being resolved should not have any implementation other than accepting a set of its own dependencies.

这篇关于我应该使用 ILogger、ILogger<T>、ILoggerFactory 或 ILoggerProvider 作为库吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆