dependency injection和命名logging器

我有兴趣进一步了解人们如何使用dependency injection平台来注入日志。 尽pipe下面的链接和我的示例都提到了log4net和Unity,但我不一定会使用这两者中的任何一个。 对于dependency injection/ IOC,我可能会使用MEF,因为这是项目其他部分(大)正在解决的标准。

我对dependency injection/ ioc非常陌生,对于C#和.NET来说很新颖(在VC6和VB6的过去10多年中,在C#/ .NET中写了很less的生产代码)。 我已经做了很多调查,在那里的各种日志logging解决scheme,所以我认为我有一个体面的处理他们的function集。 我只是不太熟悉,用实际的机制来获得一个dependency injection(或者,也许更“正确”,得到一个注入的dependency injection的抽象版本)。

我已经看到其他与日志和/或dependency injection相关的post,如: dependency injection和日志接口

logging最佳实践

Log4Net Wrapper类是什么样的?

再次关于log4net和Unity IOCconfiguration

我的问题没有具体与“如何使用ioc工具yyy注入日志logging平台xxx”? 相反,我感兴趣的是人们如何处理包装日志平台(通常,但并不总是推荐)和configuration(即app.config)。 例如,以log4net为例,我可以configuration(在app.config中)一些logging器,然后以这样的代码的标准方式获得这些logging器(没有dependency injection):

private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 

另外,如果我的logging器不是为一个类命名的,而是一个function区域,我可以这样做:

 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. 我想能够解决一个特定的命名logging器实例(可能共享相同的命名实例的所有请求者之间的实例)直接或间接的某种dependency injection,可能是MEF。

  3. 我不知道我是否会称这是一个困难的要求,但我希望有能力得到一个命名logging器(不同于类logging器)的需求。 例如,我可能会根据类名为我的类创build一个logging器,但是一种方法需要特别重的诊断,我想单独控制它们。 换句话说,我可能希望单个类“依赖”两个独立的logging器实例。

让我们从数字1开始。我已经阅读了大量的文章,主要是在这里的stackoverflow,是否是一个好主意换行。 请参阅上面的“最佳实践”链接,并转到jeffrey hantin的一个观点,即为什么打包log4net不好。 如果你包装(如果你能有效地包装),你会严格包装注入/消除直接倾向的目的? 或者你也会尝试抽象掉一些或全部的log4net app.config信息?

比方说,我想要使用System.Diagnostics,我可能想要实现一个基于接口的logging器(甚至可能使用“通用”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号…如何解决一个特定的命名logging器实例? 我应该只是利用我select的日志平台的app.config信息(即根据app.config中的命名schemeparsinglogging器)? 所以,在log4net的情况下,我可能更喜欢“注入”LogManager(注意,我知道这是不可能的,因为它是一个静态对象)? 我可以打包LogManager(称之为MyLogManager),给它一个ILogManager接口,然后parsingMyLogManager.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容器中。 之后,当请求相同名称的logging器时,会为该名称查询包装的LogManager。 如果ILogger在那里,就这样解决。 如果MEF可以做到这一点,那么这样做有什么好处吗?

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

我知道我可以像这样从MEF容器中获取命名实例:

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

但是,如何将命名实例放入容器? 我知道基于属性的模型,我可以有不同的ILogger实现,每个实例都被命名(通过MEF属性),但是这对我没有任何帮助。 有没有办法像app.config(或其中的一个部分)那样创build类似logging器(所有相同的实现)的名称,并且MEF可以读取? 可以/应该有一个中央“经理”(如MyLogManager),通过底层的app.configparsing命名的logging器,然后将parsing的logging器插入到MEF容器? 通过这种方式,对于有权访问相同MEF容器的其他人可以使用(尽pipe没有MyLogManager有关如何使用log4net的app.config信息的知识,似乎该容器将无法直接parsing任何指定的logging器)。

这已经很长了。 我希望它是连贯的。 请随意分享关于如何dependency injection日志平台的任何特定信息(我们很可能将log4net,NLog或build立在System.Diagnostics上的某些内容(希望尽可能简化)纳入您的应用程序中)。

你注入了“经理”,并让它返回logging器实例吗?

您是否在自己的configuration部分或DI平台的configuration部分中添加了一些自己的configuration信息,以便于/可以直接插入logging器实例(即,使您的依赖项位于ILogger而不是ILogManager上)。

有一个静态的或全局的容器,其中有ILogManager接口或者其中的一组命名的ILogger实例。 因此,不是按照传统的意义(通过构造函数,属性或成员数据)进行注入,而是根据需要明确地parsing日志依赖性。 这是dependency injection的好方法还是坏方法?

我把这个标记为一个社区维基,因为它似乎不是一个有明确答案的问题。 如果有人有其他感觉,请随时更改。

谢谢你的帮助!

我正在使用Ninject来parsinglogging器实例的当前类名称,如下所示:

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

NLog实现的构造函数可能如下所示:

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

我猜,这个方法也应该和其他IOC容器一起工作。

也可以使用Common.Logging外观或简单logging外观 。

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

坦率地说,日志是我自动注入很less或没有任何价值的依赖关系之一。

我需要日志服务的大部分类都是这样的:

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

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

一个难以避免的警告是特定于一个日志框架或者另一个日志框架的function丧失,或许是最常见的例子是自定义日志logging级别。

这是为所有想要了解如何注入logging器依赖项的人提供一个日志logging平台(如log4net或NLog)的好处。 我的问题是,当我知道特定的ILogger的parsing将依赖于知道依赖于ILogger的类的types时,我无法理解如何使一个类(例如MyClass)依赖于一个ILoggertypes的接口例如MyClass)。 DI / IoC平台/容器如何获得正确的ILogger?

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

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

虽然AutoFac和StructureMap没有提供日志工具(至less我可以通过查看),但他们似乎提供了实现自定义parsing器的function。 所以,如果你想编写你自己的日志抽象层,你也可以编写一个相应的自定义parsing器。 这样,当容器想要parsingILogger时,你的parsing器将被用来获得正确的ILogger,并且它可以访问当前的上下文(即当前正在满足什么对象的依赖关系 – 哪个对象依赖于ILogger)。 获取对象的types,然后准备将ILogger的创build委托给当前configuration的日志平台(您可能已经抽象了一个接口并为其编写了parsing器)。

所以,我怀疑需要几个关键点,但以前我并没有完全掌握:

  1. 最终DI容器必须知道使用什么日志平台。 通常这是通过指定“ILogger”由特定于日志logging平台的“parsing器”(因此Castle具有log4net,NLog和System.Diagnostics“parsing器”(等等))来解决的。 使用哪个parsing器的规格可以通过configuration文件或编程来完成。

  2. parsing器需要知道依赖关系(ILogger)正在parsing的上下文。 也就是说,如果MyClass已经创build并且依赖于ILogger,那么当parsing器试图创build正确的ILogger时,它(parsing器)必须知道当前的types(MyClass)。 这样,parsing器就可以使用底层的日志实现(log4net,NLog等)来获取正确的日志。

这些问题对于那些DI / IoC用户来说可能是显而易见的,但是我现在刚刚进入这个阶段,所以花了我一些时间才想起来。

我还没有想到的一件事情是如何或如果这样的事情是可能的MEF。 我可以有一个依赖于接口的对象,然后让我的代码在MEF创build对象之后执行,而接口/依赖关系正在parsing? 所以,假设我有这样的一个类:

 public class MyClass { [Import(ILogger)] public ILogger logger; public MyClass() { } public void DoSomething() { logger.Info("Hello World!"); } } 

当MEFparsingMyClass的导入时,我可以有一些自己的代码(通过一个属性,通过一个额外的接口实现ILogger,在其他地方?)执行和解决的ILogger导入的事实,因为它是MyClass,目前在上下文中,并返回一个(可能)不同的ILogger实例将比YourClass检索吗? 我是否实施某种MEF提供者?

在这一点上,我还不了解MEF。

我看到你想出你自己的答案:)但是,对于未来的人有这个问题,关于如何不绑定到一个特定的日志框架,这个库: Common.Logging帮助完全是这种情况。

我做了自定义的ServiceExportProvider ,由提供者注册Log4Netlogging器,以便MEFdependency injection。 因此,您可以使用logging器进行不同种类的注射。

注射的例子:

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