【问题标题】:How to combine logging with an exception handling chain?如何将日志记录与异常处理链结合起来?
【发布时间】:2013-03-04 08:13:47
【问题描述】:

假设我有以下代码:

void foo() {
    /* ... */
    try {
        bar(param1);
    } catch (MyException e) {
        /* ??? */
    }
}

void bar(Object param1) throws MyException {
    /* ... */
    try {
       baz(param2);
    } catch (MyException e) {
        /* ??? */
    }
}

void baz(Object param2) throws MyException {
    /* ... */
    if (itsAllATerribleMistakeOhNo) {
        /* ??? */
        throw new MyException("oops, error.");
    }
}

我想知道应该在哪里以及如何记录错误。

  • 在下面的 baz() 中发生错误的地方,我确切地知道什么操作出了差错,并且可以记录这一事实。
  • 在顶部,我有最一般的上下文(例如,在处理过程中我们遇到错误的连接的 IP 是什么。)
  • 在此过程中,我可能会遇到一些在顶部或底部都不知道的上下文。

另一个复杂情况是,当您从顶部查看时,底部的错误可能不会真正被视为错误(例如,在数据库中查找某些内容失败;也许您不确定) - 所以我可能会选择logger.WARN() 而不是 logger.ERROR()

所以,我在上面描述了 3 个位置(底部、顶部和沿途) - 但这不仅仅是在哪里记录的问题,还有要吐出什么的问题。在中间的每个级别,您都有 2x2 选项:

  • 记录/不记录消息
  • 抛出原始异常/将异常包装在带有添加消息的新异常中。

对于这些复杂的选择,有哪些最佳实践或一些常识?

注意:我不是在问一般的错误处理/异常使用,只是关于上面描述的困境。

【问题讨论】:

    标签: java logging error-handling coding-style conventions


    【解决方案1】:

    我倾向于遵循的一些建议:

    Link for some best practices

    1) 跟踪异常发生的地方。如果类或 API 知道异常发生的上下文,那么作为发生异常的点,那么跟踪并提供适当的日志会更好。但如果 API 无法处理或评论确切的上下文,则 API 不应记录事件并将其留给调用者。

    2) 包装异常 :当有很多异常可以抛出并且所有异常形成一个类似的组(SQLException)时,它提供单个异常并允许您在需要时提取信息.否则调用者需要处理的异常将会激增。

    3) 重新抛出异常:如果 API 记录异常并且用户可以对此采取一些操作,则必须重新抛出异常以告知用户发生了某些错误情况。 p>

    4) 异常的正确原因:异常消息不应过于技术性,调用者无法理解,消息本身应引导用户理解异常的根本原因。

    更新: Exception Management in Java

    【讨论】:

    • 关于您的第 1 点:您建议仅在调用链中的 1 点进行记录。你能详细说明为什么你认为这是最好的吗?关于第 2 点:包装和使用异常子类树是两件不同的事情……关于第 4 点:好点,但与我的困境无关。
    • 在跟踪消息的情况下,在发生异常时进行跟踪对开发人员很有帮助。正如您所说的那样,链条会增加异常原因。然后根据我的说法,您应该创建单独的检查异常以准确指出原因。如果是开发人员或编程错误,那么抛出运行时异常会更好。例如DBConnectivityNotAvailable 可以检查异常。你能举一个你感到困惑的例子吗?我的意思是这取决于具体情况使用什么策略进行日志记录。
    • 我的情况是一些现有的检查异常和一些潜在的运行时异常的复杂情况,目前我在调用链的顶部有一个包罗万象的块,并且偶尔会沿着链进行日志记录。还有 ERROR-vs-WARN 问题,链的底部无法知道某事是否为 ERROR。
    • 无论你采取哪种方法DOCUMENT IT.
    • 如果出现错误与警告问题,如果链的底部不知道,那么最好抛出一个已检查的异常并让上链的调用者决定在该上下文中什么是有意义的。不要捕获运行时异常,除非它在这一点上有意义(我的意思是说如果异常不应该跨越上下文边界)。在这种情况下,捕获所有异常 - 以适当的方式记录它们或重新抛出它们。
    【解决方案2】:

    谈到日志记录,我更喜欢将所有日志记录保留在应用程序边界的顶部。通常我使用拦截器或过滤器以一般方式处理所有日志记录。通过这个概念,我可以保证所有内容都只记录一次。

    在这种情况下,您将登录到您的 foo() 方法或您的应用程序的任何入口点(您提到了 IP 地址,我想我们正在谈论一个 servlet 容器或应用程序服务器)。

    然后,在过滤器/拦截器中捕获您自己的异常并根据您的需要记录它。添加catch throwable 以捕获您未在代码中处理的所有其他异常并将它们记录为错误,因为显然您错过了堆栈跟踪中更下方的某些内容。

    这个概念需要提前规划。您可能会使用自己的ApplicationException 来存储错误消息(字符串)以及一些严重级别(可能在枚举中)。当您进行实际日志记录时,您需要它来选择正确的日志级别。

    这适用于所有情况,并且具有所有日志记录仅发生一次的优点。但是,在一种情况下您仍然需要登录代码:如果您可以完全处理代码中的某个错误(即发生异常并且您可以做一些事情,让您继续工作而无需(重新)抛出一个例外)。由于您没有引发异常,因此不会记录任何内容。

    总结一下:

    • 以一般方式在最高位置登录,最好使用拦截器或过滤器。
    • 在您自己的 ApplicationExceptions 中包装异常并添加严重性以及其他感兴趣的内容以登录您的应用程序。

    【讨论】:

      【解决方案3】:

      当我在代码中抛出异常时,我通常不会记录任何内容。例外是足够的信息。 唯一的例外是,当我在我的系统边界时,也就是说,当异常将离开我的系统边界时,我会登录,因为我不确定其他系统会如何处理错误。

      当我处理异常时,我会在主动处理它们时记录它们,这意味着当我处于 catch 子句中时,它会做更多的事情,而不仅仅是重新抛出异常。 通常这是在顶部,但这取决于情况。

      【讨论】:

      • 但是:(1)当您在处理过程中部分处理事情时? (2) 在调用链的不同位置可获得不同的上下文信息?
      • 我很少遇到这种情况。更像是由于上下文更改,我必须在重新抛出异常之前转换它。这在很大程度上取决于具体情况,但通常一个 WARN 级别的日志条目应该是一个好主意。
      • 对于情况(2),如果你想处理异常或者只是声明它是由这个方法抛出的,这应该会影响你的决定。如果上下文足够处理它,那很好,如果不只是让调用堆栈的更高级别处理事情。
      【解决方案4】:

      在测试阶段抛出异常时,你应该记住:

      • 使异常消息尽可能清晰。堆栈跟踪在最好的情况下可能会令人困惑,因此请确保您正在阅读的内容至少对您有意义。
      • 确保异常与事件相关。如果用户输入了错误的值并且您抛出 NullPointerException,则您的代码不合逻辑并且失去了它的价值。
      • 确保它包含尽可能多的关于事件的信息。也就是说,保持消息的相关性。如果数据库调用出错,则将连接字符串打印到数据库,然后尝试 SQL 查询。当前使用的每个变量的状态不是必需的。
      • 不要胡扯。输入技术术语以使其看起来像是在侵入矩阵是很诱人的。在压力大的情况下它对您没有帮助,当然也对使用您的代码的其他人没有帮助。简单的英文单词总是更可取。
      • 最后,永远不要忽略异常。 始终确保处理异常,并且按照我上面所述的规则以某种方式输出详细信息。

      【讨论】:

      • 这是关于处理异常的所有好建议,但这并不是我的问题所在......
      猜你喜欢
      • 1970-01-01
      • 2011-10-20
      • 2010-11-10
      • 1970-01-01
      • 2010-09-25
      • 1970-01-01
      • 2012-10-06
      • 2020-10-01
      • 2014-10-28
      相关资源
      最近更新 更多