Внедрение зависимостей и именные регистраторы

Мне интересно узнать больше о том, как люди внедряют протоколирование с платформами внедрения зависимостей. Хотя ссылки ниже и мои примеры относятся к log4net и Unity, я не обязательно буду использовать ни один из них. Для внедрения зависимостей / IOC я, вероятно, буду использовать MEF, поскольку это стандарт, на котором основывается остальная часть проекта (большая).

Я очень новичок в внедрении зависимостей / ioc и довольно плохо знаком с C # и .NET (написал очень мало рабочего кода на C # / .NET после последних 10 лет или около того VC6 и VB6). Я провел много исследований различных решений для ведения журналов, которые существуют, поэтому я думаю, что у меня есть приличный контроль над их наборами функций. Я просто недостаточно знаком с реальной механикой ввода одной зависимости (или, может быть, более "правильно", получения абстрактной версии одной вставленной зависимости).

Я видел другие посты, связанные с ведением журнала и / или внедрением зависимости подобно: так что я думаю, что у меня есть приличная ручка для их наборов функций. Я просто недостаточно знаком с реальной механикой ввода одной зависимости (или, может быть, более "правильно", получения абстрактной версии одной вставленной зависимости).

Я видел другие посты, связанные с ведением журнала и / или внедрением зависимости подобно: так что я думаю, что у меня есть приличная ручка для их наборов функций. Я просто недостаточно знаком с реальной механикой ввода одной зависимости (или, может быть, более "правильно", получения абстрактной версии одной вставленной зависимости).

Я видел другие посты, связанные с ведением журнала и / или внедрением зависимости подобно: Внедрение зависимостей и протоколирование интерфейсов

Лучшие способы ведения журналов

Как будет выглядеть класс Log4Net Wrapper?

еще раз о log4net и конфигурации Unity IOC

Мой вопрос не имеет отношения конкретно к "Как ли я внедрить платформу журналирования xxx с помощью ioc tool yyy? " Скорее, меня интересует, как люди справляются с переносом платформы журналирования (как это часто бывает, но не всегда рекомендуется) и конфигурацией (т.е. app.config). Например, используя log4net в качестве примера, я мог настроить (в app.config) несколько регистраторов, а затем получить эти регистраторы (без внедрения зависимостей) стандартным способом использования кода, подобного следующему:

private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);

В качестве альтернативы, если мой регистратор не назван для класса, а скорее для функциональной области, я мог бы сделать это:

private static readonly ILog logger = LogManager.GetLogger("Login");
private static readonly ILog logger = LogManager.GetLogger("Query");
private static readonly ILog logger = LogManager.GetLogger("Report");

Итак, я предполагаю, что мои «требования» будут примерно такими:

  1. Я хотел бы изолировать источник моего продукта от прямой зависимости от платформы ведения журналов.

  2. Я хотел бы иметь возможность разрешать конкретный именованный экземпляр средства ведения журнала (возможно, разделяя один и тот же экземпляр среди всех запрашивающих объектов с тем же именованным экземпляром). ) прямо или косвенно с помощью какого-либо внедрения зависимости, вероятно, MEF.

  3. Я не знаю, назвал бы это жестким требованием, но мне бы хотелось иметь возможность получить именованный регистратор (отличный от регистратора классов) по требованию. Например, я мог бы создать регистратор для своего класса на основе имени класса, но один метод требует особенно тяжелой диагностики, которую я хотел бы контролировать отдельно. Другими словами, я бы хотел, чтобы один класс «зависел» от двух отдельных экземпляров регистратора.

Давайте начнем с номера 1. Я прочитал ряд статей, в первую очередь здесь, на stackoverflow, о том, является ли это хорошей идеей для переноса. См. Ссылку «Лучшие практики» выше и перейдите к комментарию Джеффри Хантин , чтобы узнать, почему плохо оборачивать log4net. Если бы вы сделали обертывание (и если бы вы могли обернуть его эффективно), вы бы обернули его строго с целью инъекции / удаления прямой зависимости? Или вы также попытаетесь абстрагировать некоторую или всю информацию log4net app.config?

Допустим, я хочу использовать System.Diagnostics, возможно, я бы хотел реализовать основанный на интерфейсе регистратор (возможно, даже используя «общий» интерфейс ILogger / ILog), возможно, на основе TraceSource, чтобы я мог внедрить его. Вы бы реализовали интерфейс, скажем, через TraceSource, и просто использовали бы информацию System.Diagnostics app.config как есть?

Что-то вроде этого:

public class MyLogger : ILogger
{
  private TraceSource ts;
  public MyLogger(string name)
  {
    ts = new TraceSource(name);
  }

  public void ILogger.Log(string msg)
  {
    ts.TraceEvent(msg);
  }
}

И использовать это так:

private static readonly ILogger logger = new MyLogger("stackoverflow");
logger.Info("Hello world!")

Переходя к номеру 2 ... Как разрешить конкретный именованный экземпляр регистратора? Должен ли я просто использовать информацию app.config выбранной мной платформы ведения журналов (т.е. разрешить регистраторы на основе схемы именования в app.config)? Итак, в случае log4net, я мог бы предпочесть «внедрить» LogManager (обратите внимание, что я знаю, что это невозможно, так как это статический объект)? Я мог бы обернуть LogManager (назовите его MyLogManager), дать ему интерфейс ILogManager, а затем разрешить интерфейс MyLogManager.ILogManager. Другие мои объекты могут иметь зависимость (на языке импорта на MEF) от ILogManager (экспорт из сборки, в которой он реализован). Теперь у меня могут быть такие объекты:

public class MyClass
{
  private ILogger logger;
  public MyClass([Import(typeof(ILogManager))] logManager)
  {
    logger = logManager.GetLogger("MyClass");
  }
}

Каждый раз, когда вызывается ILogManager, он напрямую делегирует LogManager log4net. С другой стороны, может ли обернутый LogManager взять экземпляры ILogger, которые он получает на основе app.config, и добавить их в (a?) MEF-контейнер по имени. Позже, когда запрашивается логгер с тем же именем, обернутый LogManager запрашивается для этого имени. Если есть ILogger, он решается таким образом. Если это возможно с MEF, есть ли какая-то польза от этого?

В этом случае, действительно, только «ILogManager» «внедряется», и он может раздавать экземпляры ILogger так, как это обычно делает log4net. Как этот тип внедрения (по существу, фабричный) сравнивается с внедрением именованных экземпляров регистратора? Это позволяет более легко использовать файл app.config log4net (или другой платформы ведения журналов).

Я знаю, что могу получить именованные экземпляры из контейнера MEF, например:

var container = new CompositionContainer();
ILogger logger = container.GetExportedValue("ThisLogger");

Но как мне получить именованные экземпляры в контейнер? Я знаю о модели на основе атрибутов, где у меня могут быть разные реализации ILogger, каждая из которых названа (через атрибут MEF), но это мне не очень помогает. Есть ли способ создать что-то вроде app.config (или раздел в нем), который бы перечислял регистраторы (все той же реализации) по имени и которые MEF мог читать? Может / должен ли быть центральный «менеджер» (такой как MyLogManager), который разрешает именованные регистраторы через базовый app.config, а затем вставляет разрешенный регистратор в контейнер MEF? Таким образом, он будет доступен для кого-то еще, кто имеет доступ к тому же контейнеру MEF (хотя без знания MyLogManager о том, как использовать информацию app4config log4net, кажется, что контейнер не сможет разрешить любые именованные регистраторы напрямую).

Это уже получилось довольно долго. Я надеюсь, что это связно. Пожалуйста, не стесняйтесь делиться любой конкретной информацией о том, как ваша зависимость внедрила платформу журналирования (мы, скорее всего, рассматриваем log4net, NLog или что-то (возможно, тонкое), построенное на System.Diagnostics) в ваше приложение.

Вы вводили «менеджер» и возвращали ли он экземпляры логгера?

Вы добавили некоторую свою информацию о конфигурации в свой собственный раздел конфигурации или в раздел конфигурации своей платформы DI, чтобы упростить / сделать возможным непосредственное внедрение экземпляров регистратора (т. Е. Сделать ваши зависимости зависимыми от ILogger, а не ILogManager).

Как насчет наличия статического или глобального контейнера, в котором есть либо интерфейс ILogManager, либо набор именованных экземпляров ILogger. Таким образом, вместо введения в обычном смысле (через конструктор, свойство или данные элемента) зависимость ведения журнала явно разрешается по требованию. Это хороший или плохой способ внедрения зависимостей.

Я отмечаю это как вики сообщества, так как это не похоже на вопрос с определенным ответом. Если кто-то чувствует себя иначе, смело меняйте его.

Спасибо за любую помощь!

73
задан 3 revs 23 May 2017 в 11:47
поделиться

3 ответа

Это полезно для всех, кто пытается выяснить, как внедрить зависимость регистратора, когда регистратору, который вы хотите внедрить, предоставляется платформа регистрации, такая как log4net или NLog. Моя проблема заключалась в том, что я не мог понять, как я могу сделать класс (например, MyClass) зависимым от интерфейса типа ILogger, когда я знал, что разрешение конкретного ILogger будет зависеть от знания типа класса, который зависит от ILogger ( например MyClass). Как платформа / контейнер DI / IoC получает правильный ILogger?

Итак, я просмотрел исходный код Castle и NInject и увидел, как они работают. Я также посмотрел AutoFac и StructureMap.

И Castle, и NInject реализуют ведение журнала. Оба поддерживают log4net и NLog. Castle также поддерживает System.Diagnostics. В обоих случаях, когда платформа разрешает зависимости для данного объекта (например, когда платформа создает MyClass, а MyClass зависит от ILogger), она делегирует создание зависимости (ILogger) «провайдеру» ILogger (решатель может быть более подходящим вариантом). общий термин). Реализация поставщика ILogger затем отвечает за фактическое создание экземпляра ILogger и его передачу обратно, чтобы затем внедрить в зависимый класс (например, MyClass). В обоих случаях провайдер / распознаватель знает тип зависимого класса (например, MyClass).Итак, когда MyClass был создан и его зависимости разрешаются, «преобразователь» ILogger знает, что это класс MyClass. В случае использования предоставленных Castle или NInject решений для ведения журнала это означает, что решение для ведения журнала (реализованное как оболочка над log4net или NLog) получает тип (MyClass), поэтому оно может делегировать функции log4net.LogManager.GetLogger () или NLog.LogManager.GetLogger (). (Не уверен на 100% в синтаксисе log4net и NLog, но идею вы поняли).

Хотя AutoFac и StructureMap не предоставляют средства ведения журнала (по крайней мере, я мог сказать, глядя), они, похоже, предоставляют возможность реализации настраиваемых преобразователей. Итак, если вы хотите написать свой собственный уровень абстракции журналирования, вы также можете написать соответствующий настраиваемый преобразователь. Таким образом, когда контейнер хочет разрешить ILogger, ваш преобразователь будет использоваться для получения правильного ILogger И у него будет доступ к текущему контексту (то есть, зависимости какого объекта в настоящее время удовлетворяются - какой объект зависит от ILogger). Получите тип объекта, и вы готовы делегировать создание ILogger на текущую настроенную платформу ведения журнала (которую вы, вероятно, абстрагировали за интерфейсом и для которой вы написали преобразователь).

Итак, пара ключевых моментов, которые, как я подозревал, были необходимы, но которые я не полностью понимал раньше:

  1. В конечном итоге контейнер DI должен быть каким-то образом осведомлены о том, что ведется платформа для использования.Обычно это выполняется путем указания, что "ILogger" решаться "решателем", который специфичен для платформы регистрации (следовательно, у Castle есть log4net, NLog, и "резолверы" System.Diagnostics (среди прочих)). Спецификация какой резолвер можно использовать через файл конфигурации или программно.

  2. Решатель должен знать контекст, для которого зависимость (ILogger) решается. Тот есть, если MyClass был создан и это зависит от ILogger, тогда когда решатель пытается создать правильный ILogger, он ( resolver) должен знать текущий тип (Мои занятия). Таким образом, решатель может использовать базовое ведение журнала реализация (log4net, NLog и т. д.) чтобы получить правильный регистратор.

Эти моменты могут быть очевидны для тех пользователей DI / IoC, но я только сейчас вхожу в это, поэтому мне потребовалось время, чтобы осмыслить это.

Одна вещь, которую я еще не понял, - это то, как и если что-то подобное возможно с MEF.Могу ли я иметь объект, зависящий от интерфейса, а затем выполнить мой код после того, как MEF создал объект и пока интерфейс / зависимость разрешаются? Итак, предположим, что у меня есть такой класс:

public class MyClass
{
  [Import(ILogger)]
  public ILogger logger;

  public MyClass()
  {
  }

  public void DoSomething()
  {
    logger.Info("Hello World!");
  }
}

Когда MEF разрешает импорт для MyClass, могу ли я получить часть моего собственного кода (через атрибут, через дополнительный интерфейс в реализации ILogger в другом месте ???) выполнить и разрешить импорт ILogger на основе того факта, что это MyClass, который в настоящее время находится в контексте, и вернуть (потенциально) другой экземпляр ILogger, чем был бы получен для YourClass? Реализую ли я своего рода поставщика MEF?

На данный момент я все еще не знаю о MEF.

13
ответ дан 24 November 2019 в 12:25
поделиться

Я вижу, вы придумали свой собственный ответ :) Но для людей в будущем, у которых есть этот вопрос о том, как НЕ привязывать себя к определенной структуре ведения журнала, эта библиотека: Common .Logging помогает именно в этом сценарии.

5
ответ дан 24 November 2019 в 12:25
поделиться

Можно также использовать фасад Common.Logging или Simple Logging Facade.

В обоих случаях для получения ILogger используется шаблон в стиле сервисного локатора.

Честно говоря, логирование - это одна из тех зависимостей, в автоматическом внедрении которых я не вижу практически никакого смысла.

Большинство моих классов, которым требуются службы логирования, выглядят следующим образом:

public class MyClassThatLogs {
    private readonly ILogger log = Slf.LoggerService.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType.FullName);

}

Используя Simple Logging Facade, я переключил проект с log4net на NLog, и я добавил логирование из сторонней библиотеки, которая использовала log4net, в дополнение к логированию моего приложения с помощью NLog. Иными словами, фасад хорошо послужил нам.

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

16
ответ дан 24 November 2019 в 12:25
поделиться
Другие вопросы по тегам:

Похожие вопросы: