依赖注入和有名字的伐木工

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

我是依赖注入/国际奥委会的新成员,也是 C # 和 C # 的新成员。NET (只用 C #/编写了很少的生产代码。NET).我对各种各样的日志记录解决方案做了大量的调查,所以我认为我已经对它们的特性集有了一个很好的处理。我只是不够熟悉注入一个依赖项的实际机制(或者,也许更“正确”的是,注入一个依赖项的抽象版本)。

我看过其他关于日志和/或依赖注入的帖子,比如: 依赖注入和日志接口

记录最佳实践

Log4Net 包装类是什么样子的?

再次关于 log4net 和 Unity IOC 配置

我的问题与“如何使用 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)直接或间接地解析一个特定的命名 logger 实例(可能在所有具有相同命名实例的请求者之间共享相同的实例)。

  3. 我不知道这是否是一个硬性要求,但是我希望能够根据需要获得一个命名日志记录器(与类日志记录器不同)。例如,我可能根据类名为类创建一个日志记录器,但是有一个方法需要特别繁重的诊断,我想单独控制它。换句话说,我可能希望一个类“依赖”两个独立的日志记录器实例。

我们从1号开始。我已经读了很多文章,主要是关于 stackoverflow 的,关于包装是否是一个好主意。查看上面的“最佳实践”链接,并转到 Jeffrey Hantin的评论,了解为什么包装 log4net 不好。如果你做了包装(如果你能有效地包装) ,你会为了注入/去除直接依赖而严格地包装吗?或者您还会尝试抽象出 log4net app.config 的部分或全部信息吗?

假设我想使用 System。我可能希望实现一个基于接口的日志记录器(甚至可能使用“常见的”ILogger/ILog 接口) ,它可能基于 TraceSource,这样我就可以注入它。你会实现接口,比如说通过 TraceSource,然后使用 System 吗。诊断 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!")

继续讨论第二个问题... 如何解析一个特定的命名日志记录器实例?我是否应该只利用我选择的日志平台的 app.config 信息(即根据 app.config 中的命名方案解析日志记录器) ?因此,在 log4net 的情况下,我是否可以选择“注入”LogManager (注意,我知道这是不可能的,因为它是一个静态对象) ?我可以包装 LogManager (称为 MyLogManager) ,给它一个 ILogManager 接口,然后解析 MyLogManager.ILogManager 接口。我的其他对象可以依赖于 ILogManager (从实现它的程序集导出)。现在我可以有这样的物体:

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 信息,这个容器似乎不能直接解析任何命名的 logger)。

已经很久了。我希望它是连贯的。请随意分享关于如何依赖注入日志平台的任何特定信息(我们最有可能考虑 log4net、 NLog 或者在 System 上构建的其他东西(希望是瘦的)。(诊断)到您的应用程序中。

您是否注入了“ Manager”并让它返回日志记录器实例?

您是否在自己的配置部分或 DI 平台的配置部分中添加了一些自己的配置信息,以使直接注入日志记录器实例变得更加容易/可行(即将依赖关系设置为 ILogger 而不是 ILogManager)。

如果有一个静态或全局容器,其中既有 ILogManager 接口,也有一组命名的 ILogger 实例。因此,与传统意义上的注入(通过构造函数、属性或成员数据)不同,日志依赖关系是根据需要显式解决的。这是依赖注入的好方法还是坏方法。

我把它标记为一个社区维基,因为它看起来不像一个有明确答案的问题。如果有人不这么认为,请随意改变。

谢谢你的帮助!

45173 次浏览

这对于那些试图弄清楚如何在您想要注入的日志器被提供了 log4net 或 NLog 之类的日志平台时注入日志器依赖关系的人来说是有好处的。我的问题是,当我知道特定 ILogger 的分辨率取决于知道依赖于 ILogger 的类的类型(例如 MyClass)时,我不能理解我如何使一个类依赖于 ILogger 类型的接口(例如 MyClass)。DI/IoC 平台/容器如何获得正确的 ILogger?

我看了 Castle 和 NInject 的源代码,看到了它们是如何工作的。我还查看了 AutoFac 和 structureMap。

Castle 和 NInject 都提供了日志记录的实现。两者都支持 log4net 和 NLog。Castle 还支持 System。诊断。在这两种情况下,当平台解析给定对象的依赖关系时(例如,当平台创建 MyClass 和 MyClass 依赖于 ILogger 时) ,它将依赖关系(ILogger)的创建委托给 ILogger“提供者”(解析器可能是一个更常见的术语)。然后,ILogger 提供程序的实现负责实际实例化 ILogger 的实例并将其发送回去,然后将其注入到依赖类(例如 MyClass)中。在这两种情况下,提供者/解析器都知道依赖类的类型(例如 MyClass)。因此,在创建 MyClass 并解析其依赖项时,ILogger“解析器”知道该类是 MyClass。在使用 Castle 或 NInject 提供的日志解决方案的情况下,这意味着日志解决方案(作为 log4net 或 NLog 的包装器实现)获得类型(MyClass) ,因此它可以向下委托 log4net。日志管理器。GetLogger ()或 NLog。日志管理器。GetLogger ().(对 log4net 和 NLog 的语法没有100% 的把握,但是你已经明白了)。

虽然 AutoFac 和 structureMap 不提供日志记录功能(至少我通过查看可以看出) ,但它们似乎确实提供了实现自定义解析器的能力。因此,如果你想编写自己的日志抽象层,你也可以编写一个相应的自定义解析器。这样,当容器想要解析 ILogger 时,你的解析器将被用来获得正确的 ILogger,并且它可以访问当前上下文(也就是说,什么对象的依赖关系正在被满足——什么对象依赖于 ILogger)。获取对象的类型,就可以将 ILogger 的创建委托给当前配置的日志平台(您可能已经在接口后抽象了该平台,并为其编写了解析器)。

因此,有几个关键点我怀疑是需要的,但我以前没有完全掌握的是:

  1. DI 容器最终必须是 在某种程度上意识到 平台使用。通常这是 通过指定“ ILogger”是 由“解决方案”解决 是特定于日志平台的 (因此,城堡有 log4net,NLog, 和系统。诊断“解析器” 说明书 其中解析器可以使用 通过配置文件或编程。

  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 这一事实,并返回一个(可能)与 YourClass 检索到的 ILogger 实例不同的 ILogger 实例?我是否实现了某种 MEF 提供程序?

现在,我还不知道 MEF 的事。

我看到你已经找到了自己的答案:)但是,对于那些将来有这个问题的人,如何不把自己绑定到一个特定的日志框架,这个库: 常见的,伐木正好可以帮助解决这个问题。

也可以使用 常见的,伐木 facade 或 简单日志表面

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

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

我的大多数需要日志服务的类如下所示:

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


}

通过使用 Simple Logging Facade,我将一个项目从 log4net 切换到了 NLog,并且除了使用 NLog 记录应用程序的日志之外,还增加了使用 log4net 的第三方库的日志记录。也就是说,外观对我们很有帮助。

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

我使用 Njet 解析 logger 实例的当前类名,如下所示:

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

NLog 实现的构造函数可以是这样的:

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

我想,这种方法也应该适用于其他 IOC 容器。

我做了我的定制 ServiceExportProvider,由供应商我注册的日志4网络记录依赖注入由 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));
}
}
}