2010-08-10 4 views
67

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

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

Я видел другие должности, связанные с регистрации и/или инъекции зависимостей, как: dependency injection and logging interfaces

Logging best practices

What would a Log4Net Wrapper class look like?

again about log4net and Unity IOC config

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

Таким образом, я предполагаю, что мое «требование» было бы что-то вроде этого:

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

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

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

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

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

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

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 называется, он будет непосредственно делегировать LogManager Log4Net в. В качестве альтернативы, может ли обернутый LogManager взять экземпляры ILogger, которые он получает, на основе app.config и добавить их в контейнер (??) 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) в вашем приложении.

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

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

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

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

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

ответ

13

Это полезно для любого, кто пытается выяснить, как вводить зависимость журнала, когда регистратор, который вы хотите ввести, предоставляет платформу регистрации, такую ​​как 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 на текущую настроенную платформу регистрации (которую вы, вероятно, абстрагировали за интерфейсом и для которого вы написали резольвер).

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

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

  2. Резолютор должен знать контекст , для которого разрешена зависимость (ILogger). Это есть, если MyClass был создан и она зависит от ILogger, то когда распознаватель пытается создать правильный ILogger, он ( распознаватель) должен знать текущий тип (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, чем будет извлечен для YourClass? Я реализую какой-то поставщик MEF?

На данный момент я до сих пор не знаю о MEF.

+0

Похоже, что MEF и Unity (IoC из группы образцов и практики MS) похожи на сравнение яблок и апельсинов. Возможно, настоящий контейнер IoC - это то, что необходимо для этой конкретной проблемы, которая бы добавила точки расширяемости для добавления пользовательского разрешения зависимостей. http://stackoverflow.com/questions/293051/is-mef-a-dependency-injection-framework –

5

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

+0

Тот же комментарий, что и для qstarin. Спасибо за ваш указатель на Common.Logging. – wageoghe

15

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

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

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

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

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

} 

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

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

+0

Спасибо за отзыв о SLF и Common.Logging. Недавно я экспериментировал с этими библиотеками, и они оба довольно классные. В конце концов, я думаю, что наш проект, вероятно, не будет использовать DI для регистрации, поэтому мы можем использовать SLF или Common.Logging. – wageoghe

36

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

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

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

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

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

+5

Я использовал 'x.Request.ParentContext.Plan.Type.FullName', чтобы получить конкретное имя класса вместо имени интерфейса. – Chadwick

+0

Я всегда получаю ParentContext как null. У меня есть NLogLogger, наследующий от Loggerbase, который реализует ILogger. – Marshal

+0

Если базовый класс находится в другом проекте, убедитесь, что он снова инициализировал регистратор следующим образом: [сборка: log4net.Config.XmlConfigurator (Watch = true)] – Orhan

0

Я сделал свой заказ ServiceExportProvider, провайдер Я регистрирую регистратор Log4Net для инъекции зависимостей от 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)); 
     } 
    } 
} 
Смежные вопросы