依赖注入和命名记录器

wag*_*ghe 73 c# logging log4net dependency-injection nlog

我有兴趣了解更多有关人们如何使用依赖注入平台注入日志记录的信息.虽然下面的链接和我的示例引用了log4net和Unity,但我不一定会使用其中任何一个.对于依赖注入/ IOC,我可能会使用MEF,因为这是项目的其余部分(大)正在解决的标准.

我是依赖注入/ ioc的新手,对于C#和.NET来说还是新手(在过去10年左右的VC6和VB6中,在C#/ .NET中编写了很少的生产代码).我已经对各种日志解决方案进行了大量调查,所以我认为我对其功能集有一个不错的处理.我只是不熟悉获得一个依赖注入的实际机制(或者,可能更"正确",获得一个依赖注入的抽象版本).

我见过其他与日志记录和/或依赖注入相关的帖子,如: 依赖注入和日志记录接口

记录最佳实践

Log4Net Wrapper类看起来像什么?

再次关于log4net和Unity IOC配置

我的问题与"如何使用ioc工具yyy注入日志记录平台xxx"无关?相反,我感兴趣的是人们如何处理包装日志记录平台(通常,但并不总是建议)和配置(即app.config).例如,使用log4net作为示例,我可以配置(在app.config中)一些记录器,然后以标准方式使用这样的代码来获取这些记录器(没有依赖注入):

private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
Run Code Online (Sandbox Code Playgroud)

或者,如果我的记录器没有为某个类命名,而是为了一个功能区,我可以这样做:

private static readonly ILog logger = LogManager.GetLogger("Login");
private static readonly ILog logger = LogManager.GetLogger("Query");
private static readonly ILog logger = LogManager.GetLogger("Report");
Run Code Online (Sandbox Code Playgroud)

所以,我想我的"要求"将是这样的:

  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);
  }
}
Run Code Online (Sandbox Code Playgroud)

并像这样使用它:

private static readonly ILogger logger = new MyLogger("stackoverflow");
logger.Info("Hello world!")
Run Code Online (Sandbox Code Playgroud)

继续编号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");
  }
}
Run Code Online (Sandbox Code Playgroud)

无论何时调用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");
Run Code Online (Sandbox Code Playgroud)

但是如何将命名实例放入容器中?我知道基于属性的模型我可以有不同的ILogger实现,每个实现都被命名(通过MEF属性),但这对我没有帮助.有没有办法创建类似app.config(或其中的一个部分)的东西,它会按名称列出记录器(所有相同的实现),MEF可以读取?可能/应该有一个中央"管理器"(如MyLogManager)通过底层app.config解析命名记录器,然后将解析后的记录器插入MEF容器中吗?通过这种方式,可以访问相同MEF容器的其他人可以访问(尽管没有MyLogManager知道如何使用log4net的app.config信息,

这已经很久了.我希望它是连贯的.请随意分享有关您如何依赖注入日志记录平台的任何特定信息(我们最有可能将log4net,NLog或基于System.Diagnostics构建的内容(希望很薄)考虑到您的应用程序中).

您是否注入"经理"并让它返回记录器实例?

您是否在自己的配置部分或DI平台的配置部分中添加了一些自己的配置信息,以便更容易/可能直接注入记录器实例(即使您的依赖关系在ILogger而不是ILogManager上).

如果有一个静态或全局容器,其中包含ILogManager接口或其中包含一组命名的ILogger实例.因此,不是按照传统意义注入(通过构造函数,属性或成员数据),而是根据需要显式地解析日志记录依赖性.这是依赖注入的好或坏方式.

我将此标记为社区维基,因为它似乎不是一个有明确答案的问题.如果有人不这么认为,请随时改变它.

谢谢你的帮助!

Rob*_*aap 38

我正在使用Ninject来解析记录器实例的当前类名,如下所示:

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

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

public NLogLogger(string currentClassName)
{
  _logger = LogManager.GetLogger(currentClassName);
}
Run Code Online (Sandbox Code Playgroud)

我想这种方法也适用于其他IOC容器.

  • 我使用`x.Request.ParentContext.Plan.Type.FullName`来获取具体的类名而不是接口名. (6认同)

que*_*rin 16

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

这两种方法都使用服务定位器样式模式来检索ILogger.

坦率地说,日志记录是我认为自动注入几乎没有价值的依赖项之一.

我需要日志记录服务的大多数类看起来像这样:

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

}
Run Code Online (Sandbox Code Playgroud)

通过使用Simple Logging Facade,我已经将一个项目从log4net切换到NLog,除了我的应用程序使用NLog进行日志记录之外,我还添加了使用log4net的第三方库的日志记录.也就是说,立面对我们很有帮助.

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


wag*_*ghe 13

这是为了任何试图找出如何注入记录器依赖关系的人的利益,当你想要注入的记录器被提供一个记录平台,如log4net或NLog.我的问题是,我不明白我怎么能让一个类(如MyClass的)相关的ILogger型接口上时,我就知道,具体ILogger的分辨率将取决于知道是依赖ILogger类的类型(例如MyClass).DI/IoC平台/容器如何获得正确的ILogger?

好吧,我看过Castle和NInject的源代码,看看它们是如何工作的.我也看过AutoFac和StructureMap.

Castle和NInject都提供了日志记录的实现.两者都支持log4net和NLog.Castle还支持System.Diagnostics.在这两种情况下,当平台解析给定对象的依赖关系时(例如,当平台创建MyClass并且MyClass依赖于ILogger时),它会将依赖关系(ILogger)的创建委托给ILogger"提供者"(解析器可能更多)常用术语).然后,ILogger提供程序的实现负责实际实例化ILogger的实例并将其交还出来,然后将其注入依赖类(例如MyClass).在这两种情况下,提供者/解析器都知道依赖类的类型(例如,MyClass).因此,当MyClass被创建并且其依赖关系被解析时,ILogger"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"由特定于日志记录平台的"解析器"来解决的(因此,Castle具有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!");
  }
}
Run Code Online (Sandbox Code Playgroud)

当MEF解析MyClass的导入时,我可以拥有一些自己的代码(通过属性,通过ILogger实现的额外接口,在其他地方???)执行并解决ILogger导入的问题MyClass当前处于上下文中并且返回一个(可能)不同的ILogger实例,而不是为YourClass检索的实例?我是否实施某种MEF提供商?

在这一点上,我仍然不知道MEF.


Cub*_*anX 5

我看到你想出了自己的答案:)但是,对于将来有关于如何不将自己绑定到特定日志框架的问题的人们来说,这个库:Common.Logging正好帮助了这个场景.