记录日志的代码应该抛出异常吗?

4
我正在设计一个小型库,它应该帮助登录到不同的目标(文件、数据库等)。但是,如果发生错误(例如无法写入文件),我不确定是否应该抛出异常?
如果我抛出与记录相关的异常(logging),使用我的库的应用程序开发人员可以清楚地看到记录未按预期进行。另一方面,这可能会成为问题的额外来源。
如果我抑制异常并且只是静默地不记录,那么如果无法访问数据库等,则开发人员可能会错过重要信息。
有没有关于如何处理这个问题的建议、通用指南(如果a则b等)?

根据我目前的了解:

Microsoft 异常处理指南 指出:

✗ 不要有公共成员,可以基于某些选项抛出异常或不抛出异常。

因此,是否抛出异常的选项将违反这些准则,但如果无法确定异常的可能性,它们不会对抛出异常进行陈述。

我还在这个问题/答案中阅读到,我不应该捕获异常,因为我无法对其进行有意义的处理,但该问题针对的是企业级系统,而我的问题则在更一般的层面上提出。


7
这似乎是一个设计决策。我认为没有绝对正确的答案。 - But I'm Not A Wrapper Class
2
^ 同意。这似乎更适合 https://programmers.stackexchange.com/,尽管它可能仍然不符合 Stack Exchange 问题的一般要求,因为答案可能是有意见的。 - ajp15243
一个可能的解决办法是将异常输出到 Trace。他们可以使用你的库进行正常日志记录,但也可以选择配置 Trace,并将错误日志写入文件或 Windows 事件日志中,这应该总能成功。 - Joe Enos
4个回答

5
不要教条地遵循任何准则。请遵循准则,然后根据解决手头问题的最佳方法进行操作。
日志记录器是一种特殊的软件:它具有特殊的需求和注意事项。因此,Microsoft发布的准则不会完全适用于您的日志记录器,就像它们适用于通用软件一样。
在我的书中,根据“是否为开发环境”标志改变您的记录器行为非常好,如果为true,则抛出异常,否则可以抑制这些异常(可能在某些较少错误的介质中记录它们)。

非常正确。实际上,我之前没有考虑过这个! - MechMK1

2
有趣的是,就在这个星期我为我的一个应用程序添加了一个新的NLog目标,但它没有记录日志,也没有抱怨。所以我想到NLog团队已经就失败的日志目标做出了决定。我可以告诉你,NLog会悄悄地无法记录到目标中。
然而,这并不是完全的失败。他们有一个内部记录器,您可以使用它来调试记录器:https://github.com/NLog/NLog/wiki/Internal-Logging 您可以像这样配置它:
<nlog internalLogFile="c:\log.txt" internalLogLevel="Trace">
   <targets>
      <!-- target configuration here -->
   </targets>
   <rules>
      <!-- log routing rules -->
   </rules>
</nlog>

NLog(以及Log4Net)可能是.NET最大的日志记录库,这对您可能很有兴趣。

我看到的另一件事是,一些库引用了https://www.nuget.org/packages/Common.Logging/,我觉得非常优雅。它是一个非常轻量级的包,提供了一组接口,您可以使用这些接口进行日志记录。然后通过为NLog或Log4Net使用适配器并继续使用现有的目标来控制输出。


NLog的策略只是将问题转移,而不是解决问题。如果在使用内部记录器时C:磁盘已满(或用户配额超限),会发生什么?它会抛出异常吗? - Frédéric Hamidi

1

正如评论中已经提到的,这是一个设计决策,因此在技术上没有正确的答案。

我通常会像这样做:

  • 如果出现问题,那么我首先会抛出异常。
  • 我在日志框架内部捕获此异常并将其写入系统事件日志(这应该总是成功的)。
  • 如果由于某种原因它也失败了,我会默默地吞下错误。
  • 可选地,在调试版本中,您可以重新抛出异常或将其写入调试输出。

0
我建议忽略微软引用的声明,因为在记录类的模式中,我建议有一个方法返回“日志抑制”标记并抑制在记录期间发生的异常,但是还有一个方法取消日志抑制标记的效果,并根据传入的参数返回已被抑制的异常列表或者当列表不为空时,抛出包含该列表的复合异常。我进一步建议,记录请求要求以后进先出的方式使用标记,并且在取消早期创建的标记的效果之前未取消一个标记的效果应被视为异常值使用错误。
即使这与微软的建议相反,基于参数的方法是否抛出提供了两个无法通过其他方式获得的优点:
  1. 有许多情况下,支持try/do模式的操作会调用支持相同模式的方法。如果公共的try/do方法链接到一个使用参数来区分“try”和“do”的方法,并且要调用的嵌套方法也支持这样的参数,则可以使“try”和“do”方法共享相同的实现。如果所有公共方法都是“仅try”或“仅do”,“do”希望内部方法中的问题出现为它们抛出的异常,“try”不希望嵌套的“do”方法抛出它必须捕获的异常,那么即使调用者尝试在内部使用共享代码,他们最终还是需要:

    if (throwOnException)
    {
      thing.ReadData(whatever);
      success = true;
    }
    else
    {
      success = thing.TryReadData(whatever);
    }
    

    这是一段足够大的代码片段,应该是自己的方法,但从语义上讲更属于thing而不是客户端。

  2. try/finally块只有在tryfinally部分都成功时才能退出而不抛出异常。然而,如果try中发生异常,则通常最好让该异常传播,而不是被finally中抛出的异常覆盖。通常,finally代码的行为无论try块是否成功都将是相同的,除了try块成功时发生的异常应该被允许传播,而当try块失败时发生的异常应该被压制。

无论是 C# 还是 VB,都没有提供一个漂亮的方式供 finally 块知道 try 是否成功,但这是可能的。如果 finally 块有时希望抛出异常,有时则希望压制它们,但除此之外表现相同,则具有选择问题报告行为的参数会比对待挂起异常和未挂起异常的情况使用不同的代码更加简洁。


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