在从 ASP.NET Core Web 应用程序调用的动态发现的 .NET Core class 库中注入记录器的合适模式是什么?

What is a suitable pattern for injecting loggers within dynamically-discovered .NET Core class libraries called from ASP.NET Core web apps?

概览

我正在尝试将一些基于 .NET Framework 的项目移植到 .NET Core。这涉及移植许多 class 库以及使用这些库的顶级 console/web 应用程序。

我的部分要求是我的顶级应用程序应该支持基于插件的系统,在该系统中我可以通过引用这些 class 库的不同子集轻松地交换功能。我已经使用 MEF 来执行此操作。例如,我的一个 ASP.NET 核心 Web 应用程序涉及通过 ICommunicationService 与设备通信,并且我有不同的 Nuget 包导出此服务的不同实现:

[Export(typeof(ICommunicationService))]
[Shared]
public sealed class UsbCommunicationService : ICommunicationService
{
}

重新设计 Class 库

目前,这些 class 库引用 Common.Logging 并将记录器实例化为只读静态字段:

[Export(typeof(ICommunicationService))]
[Shared]
public sealed class UsbCommunicationService : ICommunicationService
{
    ...
    private static readonly ILog Log = LogManager.GetLogger<UsbCommunicationService>();
    ....
}

我在我的顶级应用程序中使用了 Log4Net,并通过引用 Common.Logging.Log4Net 适配器在我的 class 库中促进了日志记录。

但是,我知道 ASP.NET Core 依赖于 Microsoft 的新日志记录抽象框架 Microsoft.Extensions.Logging 并且 ASP.NET Core 应用程序应该设计为通过记录器的构造函数依赖注入来支持日志记录,像这样:

public class HomeController : Controller
{
    private ILogger<HomeController> _logger;

    public HomeController(ILogger<HomeController> logger)
    {
        _logger = logger;
    }

    public IActionResult Index()
    {
        _logger.LogInformation("Index action requested at {requestTime}", DateTime.Now);
        return View();
    }
}

我不完全确定在我的新 .NET Core 库和应用程序中使用哪种日志记录框架组合 (see this related post),但我倾向于在我的 class 库中从使用 Common.Logging 切换到 Microsoft.Extensions.Logging。在那种情况下,我想知道我应该如何处理记录器的实例化。这样的事情合适吗?

using Microsoft.Extensions.Logging;
...
[ImportingConstructor]
public UsbCommunicationService(
    [Import] IUsbMessageEncoder encoder,
    [Import] IUsbMessageDecoder decoder,
    [Import] ILogger<UsbCommunicationService> logger /* Add this new import */)
{
    ...
}

换句话说,我是否应该将所有需要日志记录的 class 库切换为在构造期间注入这些记录器?

正在使用 Class 个库

基于,我觉得上面详述的方法是正确的。但是,我不确定如何使用和正确实例化 class 库中定义的服务,例如 ASP.NET 核心应用程序。

ASP.NET Core 使用自己的依赖注入服务,它与 MEF 完全分开。我不能简单地在我的网络应用程序中写类似 services.AddSingleton<ICommunicationService, UsbCommunicationService>(); 的东西,原因有两个:

  1. 这个想法是支持一个基于插件的系统,在这个系统中插件是动态发现的,因此不能被 "core" 应用程序本身。

  2. UsbCommunicationService 的其他依赖项 - IUsbMessageEncoderIUsbMessageDecoder - ASP.NET Core 的服务注入器不知道并且不会被解析.

同样,我也无法使用 MEF 获取 UsbCommunicationService 的实例,因为它无法解析对 ILogger<UsbCommunicationService>.

的引用

总结

简而言之,我正在尝试为以下问题寻找解决方案:

Microsoft.Extensions.Logging 严格来说不是一个日志框架;这是一个门面。 DebugTraceConsole 等有 built-in 实现,但这只是为了方便。在实际使用中,您可能会插入类似 Serilog 的东西。 Serilog 是实际处理日志记录的东西,而 Microsoft.Extensions.Logging 只是提供了一种抽象的 "logging" 方式,而不必实际使您的应用程序代码显式依赖于 Serilog 的 API.

Common.Logging 也是 一个门面,在这种情况下,您选择插入 log4net 作为实际使用的记录器。然而,这是可以改变的。鉴于此,您可以采取一些可能的路径。

  1. 在您的 ASP.NET 核心应用中用 Common.Logging 关闭 Microsoft.Extensions.Logging。是的,你可以做到。个人觉得Microsoft.Extensions.Logging比,不过大家可以随便用。

  2. 在您的 class 库中用 Microsoft.Extensions.Logging 关闭 Common.Logging。如果您无论如何都要重写它们,这可能最有意义,但就代码中需要更改的内容而言,它也涉及最多的摩擦。

  3. 为使用 Microsoft.Extensions.Logging 的 Common.Logging 编写适配器。诚然,这有点元,但从技术上讲,简单地使用一个外观与另一个外观一起工作以最终使用特定的日志记录框架并没有错。这就是适配器模式的全部要点。这也让您两全其美:您不需要对库或 ASP.NET 核心应用程序进行太多更改。但是,由于多个外观在起作用,它确实会增加代码的熵。虽然不可能说,但我实际上并没有看到 Common.Logging 继续得到维护。既然微软有一种几乎 built-in 的外观,我希望看到它占据主导地位。现在跳槽可能是最好的选择,因为您已经进行了一些返工。