应记录哪些层的异常?

Which layers should be logging for exceptions?

提问人:Sazzad Hissain Khan 提问时间:1/12/2020 最后编辑:Sazzad Hissain Khan 更新时间:1/21/2020 访问量:1894

问:

我有一个大型单体应用程序,有四层用于满足特定的功能要求。

UI Layer -> Presentation Logic Layer -> Business Logic Layer -> Persistent Layer

呼叫流的一个最小工作示例可以是这样的,

class ProductViewController {
    func showProduct(list){
        // populate list in view
    }
}

class ProductPresenter {
    func sanitiseProduct(list){
        // apply presentation logic to list
        viewController.showProduct(list)
    }
}

class ProductService {
    func filerProducts(list){
        // apply filtering logic to list
        productPresenter.sanitiseProduct(list)
    }
}


class ProductDatabase {
    func retrieveProducts(){
        // retrieve raw product list
        productService.filerProducts(getAllProduct())
    }
}

现在,如果在流程的任何一层(即)中发生任何异常,我决定使用适当的 TAG 和信息将其记录在每一层中,然后扔回上层进行传播,以便在调试时,每一层都可以使用适当的 TAG 过滤自己的日志,而无需查看其他层(即)。query exception in Database layerespecially when different teams are responsible for different layers

在审查时,我的一位同事评论说,在我的设计中,单个异常/错误的日志会重复,这可能会降低性能和内存。他的建议是将日志记录应用于特定异常的其中一个层(即 )。但是,他建议继续将例外抛给上层。query exception in Persistent Layer only

为了性能和内存,是否应该更改提供更好可维护性的日志记录方法?处理这种情况的一般建议是什么?

日志记录 设计模式 与语言无关

评论

1赞 John Wu 1/12/2020
在 DEBUG 级别的所有位置进行日志记录。仅当代码采用不同的逻辑路径时,才在 ERROR 级别进行记录,即不只是重新抛出。但希望你不会得到太多这样的记录,以至于记录它们是一个性能问题。
0赞 Guy Coder 1/15/2020
我对这个问题思考得越多,我就越意识到它是主观的,而不是客观的。我会投反对票,但你给了它赏金,所以你真的想要一个答案。虽然我赞扬你认真思考你的问题,但也许 StackOverflow 是错误的问题。我会推荐另一个网站,但我不知道一个,所以目前你正在做的是最好的行动方案。 :)

答:

5赞 JohanSellberg 1/14/2020 #1

答案很可能是可怕的。这要视情况而定。我的意思是,如果你有性能或内存方面的问题,那么每一点都会有所帮助。此外,重复的日志条目可能会带来其他问题(例如每个团队都会查看日志条目/错误,即使它与他们无关。花时间查看不相关的日志条目可能会浪费团队的宝贵时间,即使他们能够很快看到标记)。如果这是一个问题,则仅在源中记录它可能是一件好事。

也就是说,可维护性等也是积极的因素,应该特别考虑那些最有可能长期存在的大型单体应用。我曾多次陷入让事情变得过于复杂的陷阱,希望构建完美的解决方案,但增加的复杂性使它难以维持,以至于它产生了相反的效果,即非常糟糕。

因此,我的建议是,如果当前没有内存、性能等方面的问题,那么可维护性将胜过长期的单体应用程序。但我想这个答案可能因开发人员而异。

评论

1赞 jaco0646 1/17/2020
我 100% 同意可维护性比性能更重要;对我来说,较小的日志比较大的日志更易于维护。
0赞 Sazzad Hissain Khan 1/18/2020
日志越小意味着信息越少,为什么你认为它更易于维护?@jaco0646
0赞 jaco0646 1/22/2020
@SazzadHissainKhan,一般来说,更少的东西更易于维护(尤其是更少的代码)。当涉及到日志时,更多的日志记录是更多的噪音,这使得更难找到埋藏在那里的任何信息。换一种说法,大原木的信噪比一般都很差。
1赞 John Smith 1/21/2020 #2

当您无法恢复时,只需重新抛出异常并在拦截器层(中间件)中捕获它。

否则,您可以遵循以下有趣的域模式:https://martinfowler.com/articles/domain-oriented-observability.html

3赞 Milan 1/21/2020 #3

我同意其他关于过度设计可维护性陷阱的陷阱(或你预见到的任何其他原因)——对我来说,它很少得到回报,这意味着我不得不在未来的某个时候重新设计。当我在处理大型企业应用程序时,我确实在不同的层中使用了异常句柄和抛出 - 具有类似分层架构的单体应用程序,应用程序日志分析起来是噩梦。

我假设您没有使用异常来控制应用程序流,这是一种反模式。

根据我的经验,异常应该由负责层处理,并根据调用层的“契约”返回结果。这使得层之间的接口非常紧密,当商定的接口不被遵守时,下层会抛出异常(例如 IllegalArgument)

如果处理异常不能产生有效的返回结果(例如。数据库连接丢失),则处理异常的层可以抛出通用的“层异常”(例如。数据层抛出的“DataAccessError”)。这个异常只能由能够根据合约将结果返回给上层的层捕获,否则不应处理。在示例中,最终表示层将优雅地处理异常。

无论哪个层正在处理异常,它都应该记录它,在这种情况下,您只需要一种方法来区分日志中哪个层日志行来自哪个层,因此对于上面的示例,日志将如下所示......

[PRESENTATION] Error displaying Product Info: DataAccessLayerError - Error fetching Product info for ID 123.
...
...
[DATA] Error fetching Product info for ID 123:
/stack trace of caught DB Connection Error/

这与打印线程上下文(或日志记录框架中的等效项)相结合,可以更轻松地跟踪日志文件中的错误。

1赞 Chris 1/21/2020 #4

您可以尝试遵循此域模式 https://martinfowler.com/articles/domain-oriented-observability.html 除了代码不可维护之外,通常不能带来良好的性能提升。

0赞 Asad Shakeel 1/21/2020 #5

理想情况下,答案是 .至于例外情况,可以在 .Business Logic LayerPersistence LayerBusiness Logic Layer

另外,工作是从 中获取数据,将其反序列化并发送到 ,然后从序列化中获取数据并将其发送到 .Presentation LayerUI LayerBusiness Logic LayerBusiness Logic LayerUI Layer

这也取决于软件的架构。您还可以在每一层中记录错误,并使用唯一标识符来查找特定操作。例如,使用 HTTP 请求的软件应在每个日志语句中使用唯一的 a 来标识请求的所有操作,而对于消息传递(队列)系统,可以类似地使用消息传递(队列)系统来标识操作的日志。requestIdmessageId

0赞 Radwan Abu-Odeh 1/21/2020 #6

第一个想法是将日志记录责任转移到所有异常。

通过使所有异常继承同一个 Base,它组成了系统的记录器,并且可以在抛出日志记录时触发日志记录。

这样一来,它被扔进哪一层并不重要,重要的是你让所有异常都负责它们的日志记录,因为它们拥有日志记录所需的最多信息。在GRASP中检查信息专家概念

                  +----------------------+
                  |                      |
        +---------+   LoggableException  +----------+
        |         |                      |          |
        |         +-----------+----------+          |
        |                     |                     |
        |                     |                     |
        |                     |                     |
        |                     |                     |
+-------v--------+    +-------v--------+   +--------v--------+
|                |    |                |   |                 |
|  Exception1    |    |   Exception2   |   |   Exception3    |
+----------------+    +----------------+   +-----------------+

评论

0赞 Sazzad Hissain Khan 1/21/2020
这并不能解决有关性能和可维护性问题的实际问题。仍然会有重复的日志和更干净的代码库。
0赞 John Smith 2/2/2020
如果要将“状态更改为可记录”,则必须添加和修改类定义。此外,只有使用者负责决定是否记录异常,而不是类本身。使用界面作为某种标记 - 这根本不是一个绝妙的主意。