Внедрение зависимостей и именованные регистраторы - PullRequest
72 голосов
/ 10 августа 2010

Мне интересно узнать больше о том, как люди внедряют протоколирование с помощью платформ внедрения зависимостей. Хотя ссылки ниже и мои примеры относятся к 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, о том, стоит ли оборачивать это хорошей идеей. Посмотрите ссылку "лучшие практики" выше и перейдите к комментарию jeffrey hantin , чтобы узнать, почему плохо оборачивать 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(<catalogs and other stuff>);
ILogger logger = container.GetExportedValue<ILogger>("ThisLogger");

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

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

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

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

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

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

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

Ответы [ 5 ]

37 голосов
/ 27 июня 2011

Я использую 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.

15 голосов
/ 20 августа 2010

Можно также использовать 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.Другими словами, фасад хорошо послужил нам.

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

13 голосов
/ 19 августа 2010

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

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

Castle и NInject обеспечивают реализацию ведения журнала. Оба поддерживают log4net и NLog. Замок также поддерживает System.Diagnostics. В обоих случаях, когда платформа разрешает зависимости для данного объекта (например, когда платформа создает MyClass, а MyClass зависит от ILogger), она делегирует создание зависимости (ILogger) «провайдеру» ILogger (решатель может быть более общий термин). Реализация провайдера ILogger затем отвечает за фактическое создание экземпляра экземпляра ILogger и его передачу обратно для последующего внедрения в зависимый класс (например, MyClass). В обоих случаях поставщик / распознаватель знает тип зависимого класса (например, MyClass). Итак, когда MyClass был создан и его зависимости разрешены, ILVGER "resolver" знает, что классом является 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) решается. Тот есть, если 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.

5 голосов
/ 19 августа 2010

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

0 голосов
/ 21 ноября 2016

Я сделал свой пользовательский 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));
        }
    }
}
...