2010-08-10 53 views
67

我有兴趣了解更多关于人们如何使用依赖注入平台注入日志的知识。虽然下面的链接和我的示例都提到了log4net和Unity,但我不一定会使用其中的任何一个。对于依赖注入/ IOC,我可能会使用MEF,因为这是项目其他部分(大)正在解决的标准。我很新的依赖注入/ ioc,并且对于C#和.NET来说很新(在VC6和VB6的过去10年左右,C#/ .NET中已经写了很少的生产代码)。我已经对各种各样的日志解决方案进行了很多调查,所以我认为我对他们的功能集有很好的把握。我只是不太熟悉用实际的机制来获得一个依赖注入(或者,也许更“正确地”,得到一个注入的依赖注入的抽象版本)。依赖注入和命名记录器

我已经看到了相关的记录和/或依赖注入其他职位,如: dependency injection and logging interfaces

Logging best practices

What would a Log4Net Wrapper class look like?

again about log4net and Unity IOC config

我的问题没有专门做“如何使用ioc工具yyy注入日志记录平台xxx?“相反,我感兴趣的是人们如何处理包装日志平台(通常,但并不总是推荐)和配置(即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接口。我的其他对象可能在ILogManager上有一个依赖关系(用MEF的说法导入)(从它实现的程序集导出)。现在我可以有这样的对象:

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

任何时候调用ILogManager,它都会直接委托给log4net的LogManager。或者,包装的LogManager能否根据app.config获取ILogger实例,并将它们按名称添加到(a?)MEF容器中。稍后,如果请求具有相同名称的记录器,则会为该名称查询包装的LogManager。如果ILogger在那里,就这样解决。如果MEF可以做到这一点,那么这样做有没有什么好处?

在这种情况下,实际上只有ILogManager是“注入”的,它可以按照log4net的正常方式发出ILogger实例。这种类型的注入(本质上是工厂)与注入指定的记录器实例相比如何?这确实可以更轻松地利用log4net(或其他日志平台)app.config文件。

我知道,我能得到命名实例出这样的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,因为它看起来不像一个有明确答案的问题。如果任何人有其他感觉,请随时更改。

感谢您的帮助!

回答

13

这是为任何想要了解如何注入记录器依赖项的人提供的帮助,当您要为其注入的记录器提供日志记录平台(如log4net或NLog)时。我的问题是,当我知道特定ILogger的解析将取决于知道依赖于ILogger的类的类型时,我无法理解如何创建一个类(例如MyClass)依赖于ILogger类型的接口(例如MyClass)。 DI/IoC平台/容器如何获得正确的ILogger?

那么,我已经看过Castle和NInject的源代码,并且看过它们是如何工作的。我也看了AutoFac和StructureMap。

Castle和NInject都提供了日志记录的实现。两者都支持log4net和NLog。 Castle还支持System.Diagnostics。在这两种情况下,当平台解析给定对象的依赖关系时(例如,当平台创建MyClass并且MyClass依赖于ILogger时),它将创建依赖项(ILogger)委托给ILogger“provider”(解析器可能更多通用术语)。然后,ILogger提供程序的实现负责实际实例化一个ILogger实例并将其返回,然后将其注入到相关类(例如MyClass)中。在这两种情况下,提供者/解析器都知道相关类的类型(例如MyClass)。所以,当MyClass被创建并且它的依赖被解析时,ILogger“解析器”知道这个类是MyClass。在使用Castle或NInject提供的日志记录解决方案的情况下,这意味着日志解决方案(作为log4net或NLog的封装实现)获取类型(MyClass),因此它可以委托给log4net.LogManager.GetLogger()或NLog.LogManager.GetLogger()。 (对于log4net和NLog不是100%确定的语法,但你明白了)。

尽管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实施额外的接口,在别处???)基于它是MyClass的事实来执行和解析ILogger导入,它现在处于上下文中,并返回一个(可能)不同的ILogger实例,而不是为YourClass检索的实例?我是否实施某种MEF提供商?

至此,我还不了解MEF。

+0

看起来像MEF和Unity(来自MS的Patterns&Practices组的IoC)就像比较苹果和橘子。也许一个真正的IoC容器是这个特定问题所需要的,它将具有可扩展性来添加自定义依赖性解决方案。 http://stackoverflow.com/questions/293051/is-mef-a-dependency-injection-framework – 2012-12-28 18:42:31

5

我看到你想出了你自己的答案:)但是,对于未来的人们来说,如何避免将自己绑定到某个特定的日志框架上,这个库:Common.Logging正好可以帮助解决这个问题。

+0

与qstarin相同的评论。感谢您指向Common.Logging。 – wageoghe 2010-09-15 16:58:06

15

也可以使用Common.Logging立面或Simple Logging Facade

这两个都使用服务定位器样式模式来检索ILogger。

坦率地说,日志记录是我在自动注入中看不到任何价值的那些依赖之一。

我的大多数类需要记录服务的是这样的:

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

} 

通过利用简单的日志门面我转一个项目从log4net的到NLOG,我已经从第三方库添加日志记录除了使用NLog的应用程序日志记录之外,还使用了log4net。也就是说,立面给了我们很好的帮助。

难以避免的一个警告是特定于一个日志框架或另一个日志框架的功能丢失,或许是最常见的例子是自定义日志记录级别。

+0

感谢有关SLF和Common.Logging的提示。我最近对这些库进行了实验,它们都很酷。最后,我认为我们的项目可能不会使用DI来获取日志,所以我们最终可能会使用SLF或Common.Logging。 – wageoghe 2010-09-15 16:57:42

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 2013-09-07 20:28:13

+0

我总是将ParentContext作为null。我有从实施ILogger的Loggerbase继承的NLogLogger。 – Marshal 2015-10-05 13:29:08

+0

如果基类在不同的项目中,那么确保再次初始化记录器,如: [程序集:log4net.Config.XmlConfigurator(Watch = true)] – Orhan 2016-08-04 11:40:40

0

我让我的自定义ServiceExportProvider,由供应商注册Log4Net记录器为MEF依赖注入。因此,您可以使用记录器进行不同类型的注射。注射

实施例:

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

结果在控制台::

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

使用示例

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