哪些层应该记录异常?
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
),我决定在每一层使用适当的 TAG 和 info 并返回到上层进行传播,以便在调试时,每一层都可以使用适当的 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 |
+----------------+ +----------------+ +-----------------+
我有一个大型单体应用程序,它有四个层以满足特定的功能需求。
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
),我决定在每一层使用适当的 TAG 和 info 并返回到上层进行传播,以便在调试时,每一层都可以使用适当的 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 |
+----------------+ +----------------+ +-----------------+