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


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

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

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

рекомендации по ведению журнала

как будет выглядеть класс-оболочка Log4Net?

снова о log4net и Unity IOC config

мой вопрос не имеет конкретного отношения к "как я могу ввести logging platform xxx с помощью инструмента ioc yyy?"Скорее, меня интересует, как люди обрабатывали обертывание платформы ведения журнала (как это часто, но не всегда рекомендуется) и конфигурацию (т. е. приложение.конфигурация.) Например, используя log4net в качестве примера, я мог бы настроить (в приложении.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. Я хотел бы иметь возможность разрешить конкретный именованный экземпляр logger (вероятно, разделяя один и тот же экземпляр среди всех запросчиков одного и того же именованного экземпляра) прямо или косвенно с помощью какой-либо инъекции зависимостей, вероятно, MEF.

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

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

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

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

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 ... Как разрешить конкретный именованный экземпляр logger? Я должен просто использовать приложение.информация о конфигурации платформы ведения журнала, которую я выбираю (т. е. разрешите регистраторы на основе схемы именования в приложении.конфигурации)? Итак, в случае log4net, могу ли я предпочесть "inject" LogManager (обратите внимание, что я знаю, что это невозможно, так как это статический объект)? Я мог бы обернуть LogManager (назовите его MyLogManager), дать ему интерфейс ILogManager, а затем разрешить MyLogManager.Интерфейс ILogManager. Мои другие объекты могут иметь depenency (импорт на языке MEF) на ILogManager (экспорт из сборки, где он реализован). Теперь у меня могут быть такие объекты:

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

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

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

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

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

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

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

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

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

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

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

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

5 69

5 ответов:

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

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

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

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

этот подход должен работать и с другими контейнерами МОК, я думаю.

можно использовать общие.Ведение журнала фасад или Простой Лесозаготовительный Фасад.

оба они используют шаблон стиля Service locator для извлечения ILogger.

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

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

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

}

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

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

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

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

замок и NInject оба обеспечивают реализацию ведения журнала. Оба поддерживают log4net и NLog. Замок также поддерживает систему.Диагностика. В обоих случаях, когда платформа разрешает зависимости для данного объекта (например, когда платформа создает 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 " является быть решается с помощью "решателя", что специфичен для платформы ведения журнала (следовательно, замок имеет log4net, NLog, и система.Диагностика " резольверы" (среди прочего.)) Спецификация из которых решатель для использования может быть сделано через конфигурационный файл или программно.

  2. решатель должен знать контекст, для которого зависимость (ILogger) решается. Что is, если MyClass был создан и это зависит от ILogger, то когда решатель пытается создайте правильный ILogger, он (the резольвер) должен знать текущий тип (MyClass). Таким образом, решатель можно использовать базовое ведение журнала реализация (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, чем будет получен для вашего класса? Могу ли я реализовать какой-то поставщик MEF?

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

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

Я сделал свой заказ ServiceExportProvider, поставщиком зарегистрировать такой как log4net регистратор для инъекций зависимости от МЭФ. В результате вы можете использовать регистратор для различных видов инъекций.

пример инъекции:

[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));
        }
    }
}