哪些层应该记录异常?

Which layers should be logging for exceptions?

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

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())
    }
}

现在,如果流程的任何层发生任何异常(即 query exception in Database layer),我决定在每一层使用适当的 TAGinfo 并返回到上层进行传播,以便在调试时,每一层都可以使用适当的 TAG 过滤自己的日志,而无需查看其他层(即 especially when different teams are responsible for different layers)。

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

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

答案很可能是可怕的..这取决于。我的意思是,如果您遇到性能或内存方面的问题,请确保每一点帮助。重复的日志条目也会带来其他问题(比如每个团队都在查看日志 entry/error,即使它与他们无关。花时间查看不相关的日志条目可能对团队来说是浪费时间,即使他们可以很快看到标签)。如果这是一个问题,仅在源头记录它可能是一件好事。

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

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

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

否则你可以遵循这个有趣的领域模式: https://martinfowler.com/articles/domain-oriented-observability.html

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

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

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

如果异常处理无法产生有效的 returned 结果(例如,数据库连接丢失),则处理异常的层可以抛出通用 'Layer Exception'(例如。'DataAccessError' 由数据层抛出)。根据约定,这个异常只能被能够return导致上层的层捕获,否则不应该被处理。在示例中,最终表示层将优雅地处理异常。

无论哪个层正在处理异常,它也应该记录它,在你的情况下,你只需要一种方法来区分日志中的日志行来自哪个层,所以对于上面的示例日志,日志看起来像这样。 ..

[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/

这与打印 Thread Context(或您的日志框架中的等效项)相结合将使跟踪日志文件中的错误变得更加容易。

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

理想情况下,答案是 Business Logic Layer。至于Persistence Layer的异常可以在Business Logic Layer处捕获。

另外,Presentation Layer的工作是从UI Layer中取出数据,反序列化后发送到Business Logic Layer,然后从Business Logic Layer中取出数据将其序列化并发送到 UI Layer.

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

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

通过使所有 Exceptions 继承自相同的 Base,它构成了系统的记录器,并且可以在任何时候触发记录正在抛出。

这样一来,它被抛入哪一层并不重要,重要的是您让所有异常负责它们的日志记录,因为它们拥有日志记录所需的最多信息。检查 Information Expert Concept in GRASP

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