【问题标题】:Which layers should be logging for exceptions?哪些层应该记录异常?
【发布时间】:2020-04-29 06:51:43
【问题描述】:

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

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)。

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

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

【问题讨论】:

  • 处处都在 DEBUG 级别登录。仅当代码采用不同的逻辑路径作为结果时才在 ERROR 级别记录,即不只是重新抛出。但希望你不会得到太多这样的记录,以至于记录它们是一个性能问题。
  • 我越想这个问题,我就越意识到它是主观的而不是客观的。我会否决它,但你给了它一个赏金,所以你真的想要一个答案。虽然我赞扬您认真考虑您的问题,但也许 StackOverflow 是错误的地方。我会推荐另一个网站,但我不知道,所以目前你正在做的是最好的行动方案。 :)

标签: logging design-patterns language-agnostic layer


【解决方案1】:

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

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

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

【讨论】:

  • 我 100% 同意可维护性比性能更重要;对我来说,小日志比大日志更易于维护。
  • 较小的日志意味着较小的信息,为什么您认为它更易于维护? @jaco0646
  • @SazzadHissainKhan,一般来说,更少的东西更易于维护(尤其是更少的代码)。说到日志,更多的日志记录是更多的噪音,使得更难找到隐藏在那里的任何信息。换句话说,大日志中的信噪比通常很差。
【解决方案2】:

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

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

根据我的经验,异常应由负责层处理,并根据与调用层的“合同”返回结果。这使得层之间的接口非常紧密,并且当不遵守约定的接口(例如 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/

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

【讨论】:

    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

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

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

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

        【讨论】:

          【解决方案6】:

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

          通过让您的所有异常继承自同一个Base,它构成了您系统的记录器,并且可以在它被抛出时触发记录。

          这样,它被扔到哪一层都无关紧要,重要的是你让所有异常对它们的日志记录负责,因为它们拥有日志记录所需的最多信息。检查Information Expert Concept in GRASP

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

          【讨论】:

          • 这并不能解决有关性能和可维护性问题的实际问题。仍然会有具有更清晰代码库的重复日志。
          • 当你想改变“状态为可记录”时,你必须添加和修改一个类定义。此外,这只是消费者决定是否记录异常的责任,而不是类本身。使用界面作为某种标记 - 这根本不是一个好主意。
          猜你喜欢
          • 1970-01-01
          • 2012-04-16
          • 2017-12-11
          • 2010-11-19
          • 1970-01-01
          • 1970-01-01
          • 2015-08-28
          • 2013-03-08
          • 2012-12-18
          相关资源
          最近更新 更多