Чи слід брати ILogger, ILogger <T>, ILoggerFactory або ILoggerProvider для бібліотеки?


113

Це може бути дещо пов’язане з передачею ILogger або ILoggerFactory конструкторам в AspNet Core? однак це стосується конкретного дизайну бібліотеки , а не про те, як реальна програма, яка використовує ці бібліотеки, реалізує свій журнал.

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

Однак я бачу декілька інтерфейсів, а зразок коду в Інтернеті іноді використовує ILoggerFactoryта створює реєстратор у ctor класу. Існує також ILoggerProviderсхожа на версія для Фабрики лише для читання, але реалізація може або не може реалізувати обидва інтерфейси, тому мені доведеться вибрати. (Фабрика здається більш поширеною, ніж постачальник).

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

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

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

Відповіді:


113

Визначення

У нас є 3 інтерфейсу: ILogger, ILoggerProviderі ILoggerFactory. Давайте розглянемо вихідний код, щоб з’ясувати їхні обов'язки:

ILogger : відповідає за написання повідомлення журналу заданого рівня журналу .

ILoggerProvider : несе відповідальність за створення примірника ILogger(ви не повинні використовувати ILoggerProviderбезпосередньо для створення реєстратора)

ILoggerFactory : ви можете зареєструвати один або декілька ILoggerProviders на заводі, який, у свою чергу, використовує їх для створення примірника 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

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


Ін'єкційна залежність

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

1. Нагнітання заводу:

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

створює Logger with Category = TodoApi.Controllers.TodoController .

2. Введення загального 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. Ці два документи пояснюють, як можна використовувати сторонні бібліотеки з .NET Core.


Це ще одна стаття Миколи Маловича, в якій він пояснює свої 5 законів ІоК.

4-й закон Ніколаса Миколи

Кожен конструктор класу, який вирішується, не повинен мати іншої реалізації, крім прийняття набору власних залежностей.


3
Ваша відповідь та стаття Стівена є найбільш правильними і гнітюючими одночасно.
bokibeg

30

Усі вони дійсні, за винятком ILoggerProvider. ILoggerі ILogger<T>що ви повинні використовувати для ведення журналів. Щоб отримати ILogger, ви використовуєте ILoggerFactory. ILogger<T>- це ярлик для отримання реєстратора для певної категорії (ярлик для типу як категорії).

Коли ви використовуєте ILoggerдля здійснення журналу, кожен зареєстрований ILoggerProviderотримує шанс обробити це повідомлення журналу. Це не дійсно для використання коду для ILoggerProviderпрямого дзвінка .


Дякую. Це змушує мене думати, що це ILoggerFactoryбуло б чудово, як простий спосіб передати DI для споживачів, маючи лише 1 залежність ( "Просто дайте мені Фабрику, і я створять власні реєстратори" ), але заважатиме використовувати існуючий реєстратор ( якщо споживач не використовує якусь обгортку). Прийняття ILoggerзагального чи ні - дозволяє споживачеві подати мені реєстратор, який вони спеціально підготували, але робить налаштування DI потенційно набагато складнішим (якщо не використовується контейнер DI, який підтримує Open Generics). Це звучить правильно? У такому випадку я думаю, що піду з фабрикою.
Майкл Стум

3
@MichaelStum - я не впевнений, що я дотримуюся вашої логіки. Ви очікуєте, що ваші споживачі будуть використовувати систему DI, але потім хочете перейняти та вручну керувати залежностями у вашій бібліотеці? Чому це здасться правильним підходом?
Damien_The_Unbeliever

@Damien_The_Unbeliever Це хороший момент. Взяти фабрику здається дивним. Я думаю, що замість того ILogger<T>, щоб взяти, я візьму ILogger<MyClass>або навіть просто ILoggerзамість цього - користувач зможе підключити його лише до однієї реєстрації та не вимагаючи відкритих дженериків у своєму контейнері DI. Нахиляючись до негенеріальної ILogger, але багато експериментуватимете протягом вихідних.
Майкл Стум

10

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

Ви можете вибрати:

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

враховуючи це, The ILoggerProvider- просто міст для обробки кожного з зареєстрованих повідомлень журналу. Використовувати його не потрібно, оскільки це не впливає на те, що ви повинні втручатися в код, він слухає зареєстрований ILoggerProvider та обробляє повідомлення. Ось про це.


1
Що стосується ILogger проти ILogger <T> щодо DI? Чи вводили б, ні?
Меттью

Це насправді ILogger <TCategoryName> в MS Docs. Він походить від ILogger і не додає нових функціональних можливостей, крім назви класу для входу. Це також забезпечує унікальний тип, тому DI вводить правильно названий реєстратор. Розширення Майкрософт розширюють неоригінальне this ILogger.
Самуель Даніельсон

8

Притримуючись питання, я вважаю, що 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, що в цьому випадку має бути негенеріальним. Ідея полягає в тому, що, обертаючи його з власним класом, ви повністю переймаєтесь тим, як користувач міг його налаштувати.


6

Підхід за замовчуванням мається на увазі ILogger<T>. Це означає, що в журналі журнали конкретного класу будуть добре видно, оскільки вони включатимуть повне ім'я класу як контекст. Наприклад, якщо повна назва вашого класу, MyLibrary.MyClassви отримаєте це в записах журналу, створених цим класом. Наприклад:

MyLibrary.MyClass: Інформація: Мій журнал інформації

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

loggerFactory.CreateLogger("MyLibrary");

І тоді журнал буде виглядати приблизно так:

MyLibrary: Інформація: Мій журнал інформації

Якщо ви робите це у всіх класах, тоді контекст буде просто MyLibrary для всіх класів. Я думаю, ви б хотіли зробити це для бібліотеки, якщо не хочете виставляти внутрішню структуру класу в журналах.

Щодо необов'язкового ведення лісозаготівлі. Я думаю, що вам завжди потрібно вимагати ILogger або ILoggerFactory в конструкторі і залишити його споживачеві бібліотеки, щоб вимкнути його або надати Logger, який нічого не робить при введенні залежності, якщо вони не хочуть вести журнал. Дуже легко повернути журнал для конкретного контексту в конфігурації. Наприклад:

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

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)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.