Инъекция зависимостей и именованные логины

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

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

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

Протоколы ведения журналов

Как выглядит класс Wrapper Log4Net?

снова о конфигурации log4net и Unity IOC

Мой вопрос не имеет особого отношения к "Как мне внедрить платформу регистрации xxx с помощью инструмента ioc 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");

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

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

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

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

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

Предположим, что я хочу использовать System.Diagnostics, я бы, вероятно, захотел внедрить логику на основе интерфейса (возможно, даже используя "общий" интерфейс ILogger/ILog), возможно, основанный на TraceSource, чтобы я мог его вводить. Внесите ли вы интерфейс, скажем, через TraceSource, и просто используйте информацию о приложении. System.infigostics.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. Мои другие объекты могут иметь зависимость (Import in MEF parlance) от ILogManager (Экспорт из сборки, где она реализована). Теперь у меня могут быть такие объекты:

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

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

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

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

var container = new CompositionContainer(<catalogs and other stuff>);
ILogger logger = container.GetExportedValue<ILogger>("ThisLogger");

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

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

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

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

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

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

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

Ответ 1

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

Хорошо, я посмотрел источник для Castle и NInject и видел, как они работают. Я также посмотрел AutoFac и StructureMap.

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

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

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

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

  • Резолютор должен знать   контекст, для которого зависимость   (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.

Ответ 2

Я использую Ninject для разрешения имени текущего класса для экземпляра журнала таким образом:

kernel.Bind<ILogger>().To<NLogLogger>()
  .WithConstructorArgument("currentClassName", x => x.Request.ParentContext.Request.Service.FullName);

Конструктор реализации NLog может выглядеть следующим образом:

public NLogLogger(string currentClassName)
{
  _logger = LogManager.GetLogger(currentClassName);
}

Этот подход также должен работать с другими контейнерами IOC.

Ответ 3

Можно также использовать Common.Logging фасад или Простой ведение журнала.

Оба из них используют шаблон стиля локатора службы для извлечения ILogger.

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

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

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

}

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

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

Ответ 4

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

Ответ 5

Я сделал свой собственный ServiceExportProvider, провайдер зарегистрировал log4Net logger для инъекции зависимостей с помощью MEF. В результате вы можете использовать регистратор для различных видов инъекций.

Пример инъекций:

[Export]
public class Part
{
    [ImportingConstructor]
    public Part(ILog log)
    {
        Log = log;
    }

    public ILog Log { get; }
}

[Export(typeof(AnotherPart))]
public class AnotherPart
{
    [Import]
    public ILog Log { get; set; }
}

Пример использования:

class Program
{
    static CompositionContainer CreateContainer()
    {
        var logFactoryProvider = new ServiceExportProvider<ILog>(LogManager.GetLogger);
        var catalog = new AssemblyCatalog(typeof(Program).Assembly);
        return new CompositionContainer(catalog, logFactoryProvider);
    }

    static void Main(string[] args)
    {
        log4net.Config.XmlConfigurator.Configure();
        var container = CreateContainer();
        var part = container.GetExport<Part>().Value;
        part.Log.Info("Hello, world! - 1");
        var anotherPart = container.GetExport<AnotherPart>().Value;
        anotherPart.Log.Fatal("Hello, world! - 2");
    }
}

Результат в консоли:

2016-11-21 13:55:16,152 INFO  Log4Mef.Part - Hello, world! - 1
2016-11-21 13:55:16,572 FATAL Log4Mef.AnotherPart - Hello, world! - 2

Реализация ServiceExportProvider:

public class ServiceExportProvider<TContract> : ExportProvider
{
    private readonly Func<string, TContract> _factoryMethod;

    public ServiceExportProvider(Func<string, TContract> factoryMethod)
    {
        _factoryMethod = factoryMethod;
    }

    protected override IEnumerable<Export> GetExportsCore(ImportDefinition definition, AtomicComposition atomicComposition)
    {
        var cb = definition as ContractBasedImportDefinition;
        if (cb?.RequiredTypeIdentity == typeof(TContract).FullName)
        {
            var ce = definition as ICompositionElement;
            var displayName = ce?.Origin?.DisplayName;
            yield return new Export(definition.ContractName, () => _factoryMethod(displayName));
        }
    }
}