日志门面的作用是什么?

49

有许多不同的日志记录库可供选择,每个库都有自己的怪癖和优点。(.NET示例:log4net、System.Diagnostics.TraceSource、NLog等)

自然的倾向是抽象出这些怪癖并使用一个日志记录门面(facade)。 (例如:Castle.Services.LoggingCommon.Logging简单日志门面)这样,如果您正在使用的某个日志记录框架过期或者有新的框架进入流行,您只需要更换实现而不改变代码。

但是,还有多个日志记录门面可供选择。鉴于许多不同的日志实现都采用了抽象方式,为什么不使用一个日志记录门面的门面呢? 如果这听起来荒谬,那么它比原始日志门面更荒谬在哪里? 一个额外的抽象层对日志框架的控制有何神奇之处?


12
http://www.joelonsoftware.com/articles/fog0000000018.html - Nick Van Brunt
@brian 我不确定你对人们自然倾向的假设是否正确。我坚持认为大多数开发者甚至都没有考虑过这个问题。 - Howiecamp
9个回答

67
我将主要从使用抽象来隔离应用程序代码与特定日志框架之间的角度进行讲解。还有其他因素可能会影响某人选择日志框架或抽象的选择和需求。
我最近花了很多时间评估各种日志框架以及第三方日志抽象。
有些人认为将其应用程序代码与特定日志框架隔离开是有价值的。您会在这里找到许多像 这个 这个 这个 (还有更多)的帖子,在这些帖子中讨论了日志记录,并且许多人认为日志框架应该被包装/抽象化。
显然,这样可以使您不受限于特定的框架。这很重要吗?您真的会更换日志框架吗?好吧,也有很多人不提及包装或那些反对它的人。如果您看一下已发布的一些日志框架包装代码示例,您还可以看到许多示例,说明至少某些人不应该包装其日志框架!
如果您最近启动了一个项目,可能已经检查了日志框架并将其缩小为两个最终候选:log4net和NLog。每个都有支持它的论点。 log4net显然是最受欢迎的,可能是表达意见者中最受欢迎的。 NLog提供非常相似的功能。从流行度来判断,log4net可能是明显的选择。基于功能,它们似乎非常相似。根据“最近的活动”(由源代码存储库的签入、博客活动或缺乏活动等指示),NLog是明显的选择。如果您一年前必须选择,您可能会选择log4net,因为它是“安全”的选择。当时不清楚NLog何时发布。在此之后的一年里,NLog经历了相当大的开发周期,几天前发布了Beta版本。
一年前该如何选择?现在选择哪一个?那时有一个明显更好的选择吗?现在哪一个更好?抽象化带来的好处之一是能够推迟选择哪一个(你甚至不必永远都选择,尽管如果你计划使用日志框架交付产品,可能还是需要选择)。你可以测试一种框架,然后再测试另一种,感受它们在你的应用程序、团队和环境中的工作方式。类似使用Common.LoggingSLF这样的东西,允许你现在就编写代码,编写到某个日志接口/API,并放置你的日志代码。如果你认为抽象提供的接口/API足以支持你的工作(而且为什么不呢?因为它本质上与log4net和NLog提供的接口/API相同),那么使用抽象基本上没有太大的危险。在开发周期中,你可能会发现一种框架比另一种更适合于你的需求。由于已经编码到了抽象层,所以在任何时候,直到你的产品出门前,你都可以自由地进行选择。
你甚至可以考虑,从头开始编写一个日志库。同样,如果你认为log4net和/或NLog的接口/API足以满足你的需求,那么你可以使用类似的API来实现自己的日志库。如果你相信这一点,那也可能是使用抽象的另一个原因。同样,你可以从今天开始编写代码(针对你的产品,而不是你的日志库),使用其他日志框架进行日志记录,直到你的“从头开始”日志库准备就绪为止。也许你真的想使用System.Diagnostics.TraceSource和Ukadc.Diagnostics(以获得类似于log4net或NLog的输出格式化功能),以便在某些平台上使用TraceSources实现的日志记录与Microsoft的日志记录“更好”地集成。使用TraceSources编写“记录器”,然后编写抽象层,以便将其插入到Common.Logging或SLF中,可能非常容易。(如果接口/API足够,你可以仅基于抽象层库的接口编写“记录器”,而无需编写额外的抽象层)。
有了这样具有说服力的论点,为什么还会有人不使用抽象层呢?哈哈,只是开个玩笑!如果一个抽象化很好,应该自己写还是使用现有的?如果你自己写一个,那么显然你必须要写它。怎么做呢?你可以定义一个接口并包装一个框架(要小心并正确地进行封装!)。之后,如果你决定想换框架,就封装那个框架。如果你小心谨慎,你不需要改变任何应用程序代码,除非你真正创建底层框架的对象。也许这很好。你避免了对某些第三方抽象化的依赖,只需实现单个包装器覆盖单个框架的“小”代价。但是,这是有成本的。在写完你的抽象之前,你不能真正写出有日志记录的大量应用程序代码,除非你有一个更好的策略将其转换为你的抽象。测试驱动两个或多个框架以决定哪个更适合你也变得更加困难。您想要“尝试”的每个框架都需要另外进行一次包装作业。如果您想要轻松地在框架之间切换(至少在开发周期内)则需要做出努力。第三方框架已经为此提供了开箱即用的功能。
哇!现在我被卖了!给我日志记录抽象化,不然我就死!
日志记录抽象化都是好处吗?有什么缺点吗?它们不可能那么好,对吧?
嗯,像购买任何东西或得到免费东西一样,你得到的就是所提供的。日志记录抽象化也不例外。Common.Logging和SLF都没有至少一个非常重要的log4net / NLog日志记录上下文能力集(GDC、MDC、NDC)。这些可以是获得足够信息记录和格式化以使你从中获得最大价值的关键。SLF不提供TraceSource抽象,也不提供IsXXXEnabled函数。Common.Logging提供TraceSource抽象。Castle.Logging为log4net和NLog暴露GDC / MDC / NDC。它还提供了TraceSource抽象。Castle的TraceSource抽象通过提供类似于log4net和NLog提供的“分层”命名功能来增强TraceSource日志记录。看起来很酷!
此外,这些项目都是以某种形式开源的。因此,根据抽象化程度,开发人员可能对保持其最新状态和添加新功能具有更多或更少的利益。据我所知,Common.Logging已经经历了几个版本,并在Spring.Net中使用。至少在历史上似乎很活跃。Castle.Logging在Castle框架中使用。因此,他们显然有“真正”的客户并获得“真实的”使用,这将有望推动更多功能实现。据我所知,SLF不作为“真实”开发平台的一部分使用,因此很难说它被多少人使用。这些平台的路线图尚不清晰。Common.Logging在其网站上列出了一些即将推出的功能,但并没有明确说明它们何时可用。网站上写着“六月”,但是是哪一年的六月呢?邮件列表的监视频率如何?对于SLF,他们的codeplex监控频率有多高?与开发人员的付费工作相比,这些“免费”项目的优先级如何?您是否能负担得起一些第三方抽象来实现所需的功能?如果您实现了某些功能并提交回以供考虑纳入产品中,他们是否会接受?
好处是,所有这些抽象的源代码都是可用的,因此您可以承担责任并进行任何修复或添加任何增强功能,而无需经过创建抽象的时间和精力。您是否喜欢Common.Logging,但真的想要log4net / NLog GDC / MDC / NDC?获取Castle的实现并将其添加到Common.Logging中。瞧!一个记录抽象,包含近100%的log4net / NLog记录API。您是否喜欢SLF但希望它具有IsXXXEnabled?实现这并不需要太多的工作。在你加上GDC / MDC / NDC之前,请继续进行。您是否喜欢Castle?(我并不太熟悉它,不确定它在Castle之外使用起来有多容易,如果这很重要的话)请注意,我没有使用过它,但是看着git上的源代码,NLog记录器抽象可能 不会保留呼叫站点信息。
将多个开放源代码项目的部分组合成一个“超级”项目(供您或公司使用)是否道德?从Common.Logging中获取并增强Castle的GDC / MDC / NDC实现是否不好?我不知道。我会让其他人回答这个问题。
一些第三方日志记录抽象提供其他功能。您可能会使用一个以log4net为例的库。您可能不想使用log4net,或者至少可能不想受其约束。Common.Logging(和可能是SLF)使您相对较容易地捕获log4net记录消息并重新路由它们通过抽象,以便在抽象的基础日志框架的日志流中捕获它们。 SLF可能提供类似的东西。当然,您可能可以使用现有的日志框架做类似的事情,无论是开箱即用还是编写自定义的log4net Appender、NLog Target或System.Diagnostics TraceListener。这些特性在我对是否在我的项目中使用第三方日志记录抽象的评估中并不是很高,因为我主要感兴趣的是抽象方面。

那么,我的立场是什么?我认为将应用程序代码与特定的日志记录框架隔离开是有价值的。对我来说,Common.Logging看起来是一个很好的抽象选择,尽管缺少一些重要的功能(GDC/MDC/NDC)并且不兼容Silverlight。如果这些功能很快可以获得将会很棒。如果必须的话,我可以很容易地实现GDC/MDC/NDC。使其兼容Silverlight可能需要更多的努力,主要是因为我对C#/.NET/Silverlight不是特别熟悉。在这些问题得到解决之前,我们将能够使用Common.Logging编写大量的应用程序代码。我们可以花时间开发我们的应用程序而不是再开发另一个日志记录库或抽象库。如果最终必须自己添加这些缺失的功能,那么我们就必须做很多工作,就像我们如果自己实现日志记录库或抽象库一样。


3
这篇文章好长啊!我同意里面的所有内容,点赞 +1。不过或许可以再简化一下排版吗?例如,有很多内容说了多次。另外,由于这篇文章非常老了,你有没有最新的见解?期间是否发生了什么变化,需要再次讨论? - julealgon
1
我没有任何要补充的,因为我的原始帖子已经包含了所有信息。在我写这篇文章的时候,我正在努力学习有关日志记录的知识。对于我们的组织而言,我们决定采用日志门面方法,具体来说是针对 .Net 的 Common.Logging。我们还决定使用 NLog 作为底层日志记录系统,尽管由于门面的存在,我们并不直接依赖于 NLog。 - wageoghe
顺便说一句:最近我偶然发现了一个名为LibLog的东西,它只是一个文件而不是依赖项。 - LosManos

9

我认为“一”(抽象级别)是这里的魔数,因为“零”太少,“二”太多。

在日志记录器门面后面交换日志记录器(级别数:1)可能会带来一些用户受益,例如新的日志记录器可以做一些旧的日志记录器无法做到的事情。我可以想象它可能是性能、支持某些类型的附加程序等方面。

很难想象从交换日志记录器门面(级别数:2)中获得用户受益。

(如果级别数为0,则可能只是糟糕的面向对象设计:您的代码中将有数千个地方引用日志记录器,如果下一个版本的日志记录器有破坏性变化怎么办。)

日志记录器门面的问题似乎是你必须选择第三方选项之一或创建自己的选项并准备长期使用它。


5

日志门面的一个重要用途是在您编写库时。嵌入依赖库始终需要一些小心,尤其是日志。

简而言之,您不希望将日志实现强加给库用户。使用一个选择得当的门面意味着他们将能够处理您库的日志与他们自己选择的框架,并且不必经过一些奇怪的依赖排除和漏洞来使您的日志框架和他们的框架共存。


3

在我的项目中,我使用System.Diagnostics.Trace.TraceError(...)System.Diagnostics.Debug.Print(...)作为记录日志的外观。为了组织(编写)日志,我使用NLog,也就是说,在app.config文件中,我有NLog的配置和将.net的跟踪重定向到NLog。

<nlog xmlns="http://www.nlog-project.org/schemas/NLog.xsd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
   <targets>
     <target name="file" xsi:type="File"
      layout="${longdate:universalTime=true}Z [${threadid}] ${pad:padding=5:inner=${level:uppercase=true}} ${logger} ${message}"
      fileName="${basedir}/App_Data/logfile.txt"...
   </targets>
</nlog>
<system.diagnostics>
  <trace>
    <listeners>
      <add name="nlog" type="NLog.NLogTraceListener, NLog" />
    </listeners>
  </trace>
</system.diagnostics>

这不会使我与任何日志记录器捆绑在一起。当我向客户交付我的组件时,他们可以使用任何他们喜欢的日志记录器。在应用程序中使用特定的日志记录器可能会导致问题,例如,您可以使用nlog,但是您的客户使用log4net。


3
直到NLog和log4net提供可以替代具体类使用的接口之前,我一直将它们抽象在自己的接口和包装类后面。
为什么?
为了获得所有用户要求的最高测试覆盖率,特别是当这些要求涵盖日志记录时。
当您的所有日志记录都通过接口而不是具体类进行时,非常容易提供带有记录接口的模拟对象(选择您所选的依赖注入等方式),以记录特定测试场景中进行的所有日志调用。
然后可以进行断言,如果代码更改破坏了日志记录,您的单元测试将覆盖它。
如果NLog或log4Net为其记录器提供接口,则我不需要费力提供接口和包装类,因为我可以只为测试模拟它们的接口。

1

这不是魔数,而是取决于你想要多灵活。 如果你考虑将来更改日志门面,那么你应该编写一个门面门面。 如果你只考虑更改日志,则只需要一个门面。 如果你什么都不考虑,就不要使用门面。

正如你所说的,缺点在于特殊能力。如果你正在使用它们,请编写自己的门面。


0

它可用于插件系统的协议中。不需要说“使用Some3rdParty.dll和MyPlugingApi.dll”,我只需记录 MyPlugingApi.dll 。日志门面接口将建议和记录某些使用方式,这可能会导致可读的日志和足够好的日志性能。而且,更改不会导致对插件API的更改。

为什么需要更改实现?如果当前配置文件启动速度缓慢或写入条目速度缓慢,希望扩展为没有所需第三方组件的.NET的缩小版本,与使用其他日志编写程序的其他代码库集成等情况下,就可能发生这种情况。

我还编写了另一个facade,它是本答案中思想的子代。


0

我本人并不是专家,但在我们的团队中,门面的价值不在于让我们能够更改日志框架。当然,这是我们得到的一些东西,但我们属于那种非常不可能更改框架的类别。

在我们的情况下,我们使用门面来定制日志接口以满足我们应用程序的需求。我们发现,在我们查看的所有语义框架中,它们仍然过于关注我们所称的“取证”模型的日志记录——有人在日志中寻找某些输出行,试图分析某个事件。

虽然这也是我们的一个用例,但实际上并不是我们的主要用例。我们希望拥有更多的“仪表化”框架而不是日志框架,这将使我们能够报告感兴趣的事物,甚至是我们在实施时没有想到的事物。

例如,我们的应用程序不需要“消息”来伴随事件;相反,我们的“记录器”将接受定义事件类型的枚举和表示特定状态对象(例如时间戳或其他值),并将这些序列化以便于报告、性能分析、业务价值指标等等,除了支持传统的取证。 (我们认识到,事实上,为了获得易于使用和理解的日志记录界面,增加我们实际使用它并经常使用它的可能性,我们正在使传统的取证变得更加困难)。
因此,为了简洁地给您一个总结答案,以下是我们认为从使用日志门面中获得的大致排名顺序的好处。
  1. 一致的、专门构建的接口,促进仪表化(而非仅仅是传统的日志记录,如上所述)
  2. 可模拟的测试点
  3. 可注入的日志记录器实现,适用于从本地开发到 AWS S3 或 DB 连接的所有情况(我们的应用程序使用带有构造函数依赖项注入的 Autofac)
  4. 可替换的日志记录框架,使我们能够在将来更改日志记录框架。 (顺便说一句,我们不预见会发生这种情况,因此独立地,并不能说服我们使用外观。)

最后,0个外观会失去这些好处,2个外观不会增加任何这些好处,所以对我们而言,1个外观是正确的数字。

很棒的问题,@brian! :)


0
在这个例子中,你只需要一个抽象层次就能够轻松更换日志记录器。
如果你能够轻松更换日志门面,会带来什么额外的优势呢?

如果有更好的日志门面出现,或者日志门面项目停滞不前,我就可以将其替换掉。 - Brian
:) 没错,但是在我看来,你可能用不上(YAGNI),而且有些时候你必须接受未来某个阶段你只能启动代码编辑器并进行一些更改的事实。话虽如此,如果你认为你需要它,就去做吧。但是我永远不想看到第三方日志门面门面门面项目... - Christopher Edwards

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接