Должен ли я взять ILogger, ILogger <t> , ILoggerFactory или ILoggerProvider для библиотеки?

Это может быть связано с тем, что Pass ILogger или ILoggerFactory являются конструкторами в AspNet Core? , однако это особенно касается Библиотечного дизайна, а не того, как фактическое приложение, использующее эти библиотеки, реализует его ведение журнала.

Я пишу библиотеку.net Standard 2.0, которая будет установлена через Nuget, и чтобы пользователи, использующие эту библиотеку, могли получить некоторую информацию об отладке, я в зависимости от Microsoft.Extensions.Logging.Abstractions позволяет стандартизованному Logger быть введенным.

Тем не менее, я вижу несколько интерфейсов, а пример кода в Интернете иногда использует ILoggerFactory и создает регистратор в ctor класса. Там также ILoggerProvider который выглядит как версия Factory только для чтения, но реализации могут или не могут реализовывать оба интерфейса, поэтому мне нужно будет выбрать. (Завод кажется более распространенным, чем поставщик).

Некоторый код, который я видел, использует неосновный интерфейс ILogger и может даже использовать один экземпляр одного регистратора, а некоторые принимают ILogger<T> в своем ctor и ожидают, что контейнер DI поддерживает открытые типы общего типа или явную регистрацию каждого и каждая ILogger<T> используемая моей библиотекой.

Прямо сейчас, я думаю, что ILogger<T> - правильный подход, и, возможно, ctor, который не принимает этот аргумент и просто передает Null Logger. Таким образом, если регистрация не требуется, ни один не используется. Тем не менее, некоторые контейнеры DI выбирают самый большой ctor и, таким образом, терпят неудачу в любом случае.

Мне любопытно, что я должен делать здесь, чтобы создать наименьшее количество головной боли для пользователей, при этом при необходимости поддерживая надлежащую регистрацию.

Ответы

Ответ 1

Определение

У нас есть 3 интерфейса: ILogger, ILoggerProvider и ILoggerFactory. Давайте посмотрим на исходный код, чтобы узнать их обязанности:

ILogger: отвечает за написание сообщения журнала заданного уровня журнала.

ILoggerProvider: отвечает за создание экземпляра ILogger (вы не должны использовать ILoggerProvider напрямую для создания регистратора)

ILoggerFactory: вы можете зарегистрировать один или несколько ILoggerProvider на фабрике, которая, в свою очередь, использует их все для создания экземпляра ILogger. ILoggerFactory содержит коллекцию ILoggerProviders.

В приведенном ниже примере мы регистрируем 2 провайдера (консоль и файл) на заводе. Когда мы создаем регистратор, фабрика использует обоих этих провайдеров для создания экземпляра регистратора:

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

Итак, сам регистратор поддерживает коллекцию ILogger и записывает сообщение журнала всем им. Глядя на исходный код Logger, мы можем подтвердить, что Logger имеет массив ILoggers (т.е. LoggerInformation[]), и в то же время он реализует интерфейс ILogger.


Инъекция зависимости

Документация MS предоставляет 2 метода ввода логгера:

1. Injecting the factory:

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

создает Logger с Category = TodoApi.Controllers.TodoController.

2. Injecting a generic ILogger<T>:

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

создает регистратор с категорией = полное имя типа TodoController


На мой взгляд, то, что делает документацию запутанной, так это то, что в ней ничего не говорится о введении неуниверсального, ILogger. В том же примере выше мы вводим неуниверсальный ITodoRepository, и тем не менее он не объясняет, почему мы не делаем то же самое для ILogger.

Согласно Марку Симанну:

Конструктор инъекций должен делать не больше, чем получение зависимостей.

Внедрение фабрики в контроллер не является хорошим подходом, потому что контроллер не несет ответственности за инициализацию регистратора (нарушение SRP). В то же время введение универсального ILogger<T> добавляет ненужный шум. Дополнительную информацию смотрите в блоге Simple Injector: Что не так с абстракцией ASP.NET Core DI?

То, что должно быть введено (по крайней мере, в соответствии со статьей выше), не является универсальным ILogger, но это не то, что может сделать встроенный DI-контейнер Microsoft, и вам нужно использовать стороннюю DI-библиотеку.

Это еще одна статья Николая Маловича, в которой он объясняет свои 5 законов IoC.

Nikolas 4th law of IoC

Каждый конструктор разрешаемого класса не должен иметь реализация, отличная от принятия набора собственных зависимостей.

Ответ 2

Все они действительны, за исключением ILoggerProvider. ILogger и ILogger <T> - это то, что вы должны использовать для ведения журнала. Чтобы получить ILogger, вы используете ILoggerFactory. ILogger <T> является ярлыком для получения регистратора для определенной категории (ярлык для типа в качестве категории).

Когда вы используете ILogger для ведения журнала, каждый зарегистрированный ILoggerProviders получает возможность обрабатывать это сообщение журнала. Это не совсем верно для того, чтобы использовать код для прямого вызова в ILoggerProvider.

Ответ 3

ILogger<T> был фактическим, который был сделан для DI. ILogger пришел для того, чтобы легче реализовать заводскую модель, вместо того, чтобы писать самостоятельно все логики DI и Factory, это было одно из самых разумных решений в ядре asp.net.

Вы можете выбирать между:

ILogger<T> если вам нужно использовать заводские и DI-шаблоны в вашем коде, или вы можете использовать ILogger для реализации простого ведения журнала без необходимости в DI.

учитывая, что ILoggerProvider является всего лишь мостом для обработки каждого зарегистрированного сообщения журнала. Нет необходимости использовать его, так как это не влияет на то, что вы должны вмешаться в код. Он слушает зарегистрированного ILoggerProvider и обрабатывает сообщения. Это об этом.

Ответ 4

Придерживаясь вопроса, я считаю, что 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, который в этом случае должен быть неуниверсальным. Идея состоит в том, что, оборачивая его своим собственным классом, вы получаете полный контроль над тем, как пользователь может его настроить.

Ответ 5

Для дизайна библиотеки хорошим подходом было бы:

1.Не заставляйте потребителей вводить регистраторы в свои классы. Просто создайте еще один ctor, проходящий через NullLoggerFactory.

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)

Ответ 6

ILogger<T> умолчанию подразумевается ILogger<T>. Это означает, что в журнале журналы из определенного класса будут хорошо видны, потому что они будут включать полное имя класса в качестве контекста. Например, если полное имя вашего класса - MyLibrary.MyClass вы получите это в записи журнала, созданные этим классом. Например:

MyLibrary.MyClass: информация: журнал моей информации

Вы должны использовать ILoggerFactory если хотите указать свой собственный контекст. Например, все журналы из вашей библиотеки имеют один и тот же контекст журнала, а не каждый класс. Например:

loggerFactory.CreateLogger("MyLibrary");

И тогда журнал будет выглядеть так:

MyLibrary: информация: журнал моей информации

Если вы сделаете это во всех классах, тогда контекст будет просто MyLibrary для всех классов. Я предполагаю, что вы захотите сделать это для библиотеки, если не хотите раскрывать внутреннюю структуру классов в журналах.

Что касается дополнительного ведения журнала. Я думаю, что вы всегда должны требовать ILogger или ILoggerFactory в конструкторе и оставлять его для потребителя библиотеки, чтобы отключить его или предоставить Logger, который ничего не делает в инъекции зависимостей, если они не хотят регистрировать. Очень легко включить ведение журнала для конкретного контекста в конфигурации. Например:

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