多层体系结构:应该在哪里实施错误记录\处理?


17

我目前正在重构具有多层体系结构的大型子系统,并且正在努力设计一种有效的错误记录\处理策略。

假设我的架构包含以下三层:

  • 公共接口(即MVC控制器)
  • 域层
  • 资料存取层

我的困惑源是我应该在哪里实施错误日志记录\处理:

  1. 最简单的解决方案是在顶层(即Public Interface \ MVC Controller)实现日志记录。但是,这感觉不对,因为这意味着将异常遍历不同的层,然后将其记录下来。而不是将异常记录在源头。

  2. 从源头记录异常显然是最好的解决方案,因为我掌握了最多的信息。我的问题是,如果不捕获所有异常,就无法在源头捕获每个异常,并且在域/公共接口层中,这将导致捕获已被下面的层捕获,记录和重新抛出的异常。

  3. 另一种可能的策略是#1和#2的混合使用。因此,我在最有可能引发异常的层上捕获了特定异常(例如,SqlExceptions在数据访问层中捕获,记录和重新抛出异常),然后在顶层记录了任何其他未捕获的异常。但是,这还需要我在顶层捕获并重新记录每个异常,因为我无法区分已记录\处理过的错误与未记录过的错误。

现在,显然这是大多数软件应用程序中的问题,因此必须有一个解决该问题的标准解决方案,以使异常在源头被捕获并记录一次。但是我自己却看不到该怎么做。

请注意,该问题的标题与“ 在多层应用程序中记录异常 ”非常相似,但是该帖子中的答案不够详细,不足以回答我的问题。


1
我有时使用的一种方法是创建自己的Exception(或RuntimeException)子类并将其引发,包括将原始异常作为嵌套的异常。当然,这意味着在更高级别捕获异常时,我需要检查异常的类型(我自己的异常会被重新抛出,其他异常会被记录并包含在我的异常的新实例中)。但是我已经很长时间了,所以我不能给出“官方”建议。
SJuan76

4
The easiest solution would be to implement the logging at the top level- 做这个。从源头记录异常并不是一个好主意,我遇到的每个执行此操作的应用程序都是要调试的PITA。处理异常应由调用者负责。
贾斯汀

您似乎在想一些特定的实现技术/语言,否则很难理解“已记录的异常与未记录的异常”的说法。您能否提供更多背景信息?
Vroomfondel

@Vroomfondel-你是对的。我正在使用C#,并将代码包装try{ ... } catch(Exception ex) { Log(ex); }在每一层中,将导致在每一层记录相同的异常。(在代码库的每一层捕获每个异常似乎也很糟糕。)
KidCode

Answers:


18

对您的问题:

最简单的解决方案是在顶层实施日志记录

使异常气泡达到最高层是绝对正确和合理的方法。更高层的方法都没有尝试在失败后继续执行某些过程的方法,通常这不会成功。功能齐全的异常包含记录所需的所有信息。而对异常不做任何事情可以帮助您保持代码的整洁,并专注于主要任务而不是失败。

从源头记录异常显然是最好的解决方案,因为我掌握了最多的信息。

一半正确。是的,那里有最有用的信息。但是我建议将所有这些内容放入异常对象(如果尚不存在),而不是立即对其进行记录。如果您以较低级别登录,则仍需要抛出异常以告知呼叫者您尚未完成工作。这最终会出现在同一事件的多个日志中。

例外情况

我的主要指导原则是仅在顶层捕获和记录异常。并且下面的所有层均应确保将所有必要的故障信息传输到顶层。在单进程应用程序(例如Java)中,这主要意味着不要在顶层以外的所有位置尝试/捕获或登录。

有时,您希望某些异常日志中包含一些上下文信息,这些上下文信息在原始异常中不可用,例如,引发异常时执行的SQL语句和参数。然后,您可以捕获原始异常,然后重新引发一个新异常,其中包含原始异常和上下文。

当然,现实生活有时会干扰:

  • 在Java中,有时您必须捕获一个异常并将其包装为其他异常类型,以便遵守某些固定的方法签名。但是,如果您重新抛出异常,请确保重新抛出的异常包含以后记录所需的所有信息。

  • 如果您跨进程间边界,通常从技术上讲,您通常无法传输完整的异常对象,包括堆栈跟踪。当然,连接可能会丢失。因此,在这里,服务应该记录异常,然后尽最大努力将尽可能多的故障信息跨线传输到其客户端。该服务必须通过接收失败响应或在连接断开的情况下超时来确保客户端收到失败通知。通常,这将导致两次记录相同的失败,一次记录在服务内部(详细信息),一次记录在客户端的顶层。

记录中

我要添加一些有关一般日志记录的语句,而不仅仅是异常日志记录。

除了特殊情况外,您还希望应用程序的重要活动也记录在日志中。因此,请使用日志记录框架。

注意日志级别(读取调试信息和严重错误未相应标记为不同的日志很痛苦!)。典型的日志级别为:

  • 错误:某些功能无法恢复。那不一定意味着您的整个程序都崩溃了,但是某些任务无法完成。通常,您有一个描述故障的异常对象。
  • 警告:发生了一些奇怪的事情,但没有导致任何任务失败(检测到奇怪的配置,临时连接中断导致重试等)。
  • 信息:您希望将一些重要的程序动作传达给本地系统管理员(以其配置和软件版本启动某些服务,将数据文件导入数据库,用户登录系统,您便会明白...)。
  • 调试:开发人员在调试某些问题时希望看到的东西(但是您永远不会事先知道遇到这种或那种特定错误时真正需要什么-如果可以预见,您将修复该错误)。始终有用的一件事是在外部接口上记录活动。

在生产中,将日志级别设置为INFO。结果对系统管理员应该很有用,这样他就知道发生了什么事。期望他就日志中的每个错误和一半的警告呼叫您以寻求帮助或错误修复。

仅在实际调试会话期间启用DEBUG级别。

将日志条目分组为适当的类别(例如,根据生成条目的代码的完全合格的类名),从而可以为程序的特定部分打开调试日志。


感谢您提供如此详细的答案,我也非常感谢日志记录部分。
KidCode

-1

我为下挫投票作好准备,但我会弯腰说我不确定是否可以同意。

鼓吹异常,少得多地记录日志,是额外的努力,却收效甚微。从源头捕获异常(是,最简单),将其记录下来,但是然后不要重新抛出异常,只需向调用者报告“错误”即可。“ -1”,空,空字符串,一些枚举(无论如何)。呼叫者只需要知道呼叫失败,几乎不需要知道可怕的细节。这些将记录在您的日志中,对吗?在极少数情况下,呼叫者确实需要详细信息,请继续进行讨论,但不要将其视为自动的默认设置。


3
通过返回值报告错误的问题是:1.如果调用方关心失败,则必须检查特殊值。但是,等等,null还是空字符串?是-1还是任何负数?2.如果呼叫者不在意(即不检查),则会导致跟原始原因无关的后续错误,例如NullPointerException。或更糟糕的是:计算将继续使用错误的值。3.如果调用者愿意,但是程序员认为此方法失败,则编译器不会提醒他。异常不存在此问题,无论是捕获还是重新抛出。
siegi '18

1
抱歉,我不得不投票反对。您所描述的方法(用特殊的返回值替换异常)是1970年代C语言起源的最新技术,现代语言有异常的原因很多,对我而言,主要的原因是正确使用异常可以使编写健壮的代码更加容易。您的“冒泡异常是一种额外的努力”,这是绝对错误的:只对异常不做任何事情,它们会自行冒出来。
拉尔夫·克莱伯霍夫
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.