Java:在错误堆栈跟踪中,我们通常不希望向用户显示哪些信息?

12

我刚学Java,对于错误堆栈跟踪中抛出并显示给我的Web应用程序最终用户的格式规则并不熟悉。

我的Oracle数据库经验表明,错误堆栈包含内部信息,例如模式和过程名称以及行号等,这些信息在调试时很有用,但我希望防止用户看到。以下是一个示例:

java.sql.SQLException : ORA-20011: Error description here
ORA-07894: at "NAME_OF_SCHEMA.PROCEDURE_NAME", line 121
ORA-08932: at line 10
我想要向用户显示的字符串是Error description here。我可以使用正则表达式提取此字符串,因为我知道(1)该字符串始终在第一行上,因此我可以提取错误堆栈的第一行,并且(2)该字符串始终以Error开头并以该行的结尾结束。[Oracle用户请注意(我不想误导您):仅当使用以Error开头的错误字符串使用RAISE_APPLICATION_ERROR时,以上内容才适用,否则文本中不会出现Error]。
我的Java问题是:
(1)是否有任何潜在敏感信息您不希望用户在错误堆栈中看到?例如,文件路径,服务器名称/IP等。
(2)Java错误堆栈跟踪是否有任何格式规则,我可以依靠提取非敏感信息?或者,其他人如何解决这个问题?
更新1:
谢谢所有至今为止的回复,它们非常有帮助。虽然许多人评论使用诸如getUserFriendlyMessage()之类的函数将错误映射到有用的用户消息,但我想知道是否有人可以扩展此映射。也就是说,对于常见错误(SQL、I/O等),可以使用什么“可靠”的标识符来搜索此错误堆栈以识别发生的错误类型,然后建议将哪个相应的文本字符串映射到此错误消息以显示给用户?@Adarshr在下面的回复中是一个很好的开始。例如,
Identified Expected   If found in error stack, display this friendly msg to user
-------------------   ----------------------------------------------------------
SQLException          An error occurred accessing the database. Please contact support at support@companyname.com.
IOException           Connection error(?). Please check your internet connection.
假定编译相关的错误不需要解决,而是关注那些在正常使用过程中终端用户可能遇到的错误。以下是一份运行时错误消息列表供参考:http://mindprod.com/jgloss/runerrormessages.html#IOEXCEPTION 另外,是否有可能仅使用堆栈跟踪的第一行来显示给用户?这个链接有点类似于我在上面原始问题中提到的东西:http://www3.ntu.edu.sg/home/ehchua/programming/howto/ErrorMessages.html 例如,如果标识符Exception经常被使用,可以简单地提取第一行之间的文本。Exception和第一行末尾之间的文本。我不知道我们是否可以依赖于Exception总是存在。

我根据你的更新已经更新了我的答案。 - Ted Hopp
请参考以下相关问题:https://dev59.com/M1vUa4cB1Zd3GeqPqBzd - Raedwald
7个回答

8
您不应该向用户显示任何无意义的信息,这些信息对大多数用户来说毫无意义,也没有帮助。正如您所怀疑的那样,它还会暴露实现内部细节,可能会暴露出恶意用户可能利用的漏洞。
相反,您应该捕获异常,记录它们,并向用户显示更易理解的错误消息。您可以使用getMessage()提取异常的消息部分。如果异常没有消息,则显示类似“无详细信息”的内容。
更新:
根据问题的更新,我有一些评论。首先,我会完全隔离用户与系统的任何内部细节,这既是为了对用户友好,也是为了安全起见。(例如,即使知道您正在使用java.sql包,也可能向聪明的黑客暗示漏洞。)因此,在向用户显示任何内容时,请不要使用异常消息、堆栈跟踪的第一行或类似的内容。
其次,您应该将从异常级别(在您的代码中遇到的级别)发生的所有错误映射到适合用户的正确抽象级别的消息。正确的方法取决于系统的内部结构以及用户在引发异常时可能尝试执行的操作。这可能意味着将系统结构化为各个层次,每个层次捕获异常并将其转换为更高抽象层次的异常。Java异常可以包装另一个异常(原因)。例如:
public boolean copyFile(File source, File destination) throws CopyException {
    try {
        // lots of code
        return true;
    } catch (IOException e) {
        throw new CopyException("File copy failed", e);
    }
}

那么这段代码可以在用户类中的更高层次上使用:
public boolean shareFile(File source, User otherUser) throws ShareException {
    if (otherUser.hasBlocked(this) {
        throw new ShareException("You cannot share with that user.");
    }
    try {
        return copyFile(source, otherUser.getSharedFileDestination(source));
    } catch (CopyException e) {
        throw new ShareException("Sharing failed due to an internal error", e);
    }
}

我希望您清楚,上述代码旨在说明将异常转换为更高层次的抽象概念的想法,而不是建议您在系统中使用该代码。
处理此类问题(而不是某种方式的消息和/或堆栈跟踪)的原因是,异常(例如具有消息“权限被拒绝”的IOException)在不同的上下文中对用户(以及您的系统)可能意味着完全不同的事情。

感谢提供详细信息,Ted。非常感激。 - ggkmath

5
不要直接向最终用户显示异常消息/堆栈跟踪。相反,尝试使用异常-消息映射方法。
例如: - SQLException - 对不起,发生了数据库错误。请稍后再试。 - RuntimeException / Exception - 对不起,发生了错误。请稍后再试。
实际上,您可以尽可能地使其通用化。也许您可以使用自定义错误代码映射。例如,对于SQLException,可以使用E0001,Exception等,然后将其显示给最终用户。当他们使用错误代码联系客户服务时,这将有所帮助。

有趣。是否有一个关键字列表,我可以在堆栈跟踪中可靠地搜索?你上面提到了一些(例如SQLException,RuntimeException)。如果您能扩展您的答案以包括可能出现的可靠关键字和适当的响应,那将是很棒的。我喜欢你目前的回答。 - ggkmath
这真的取决于你的应用程序。这些甚至可以是自定义异常类,例如MyRuntimeExceptionNoSuchProductException - adarshr

3
您不应向终端用户显示任何他们无法理解的信息,包括完整的堆栈跟踪。当您捕获此类异常时,应该向终端用户显示以下语言的错误信息:
  • 我们的程序遇到了临时困难。请致电技术支持热线寻求帮助。
终端用户并不关心您的程序内部组织、数据库、堆栈等等。他们只知道他们认为应该正常工作的东西出现了问题,因此他们正在寻求帮助。
堆栈跟踪是用于错误日志的:您可以将它们保存给自己,或通过电子邮件发送给技术支持人员,但您不希望将其显示给终端用户。

2
我总体上同意,但在某些情况下,提供更多信息可能会有所帮助。例如,如果您遇到服务器超时问题,可能只是他们的网络连接出了问题,如果您提供这些信息,他们可能能够自行解决问题,而不必打开支持工单。 - Kevin K
1
@KevinK - 大多数用户需要知道的唯一事情是问题是否永久存在(“它没有起作用,也不会起作用”),是暂时性的(“它没有起作用,请稍后再试”)还是可以由用户纠正的(“请登录以发表评论”)。告诉用户“这是网络连接问题”将毫无意义或更糟,对非技术用户具有威慑力。程序应仅显示适合目标用户专业水平的消息。(当然,面向开发人员的应用程序可能具有更技术感,但思想仍然相同。) - Ted Hopp

1

在应用程序处于调试模式时,您应该仅显示堆栈跟踪。在生产模式下,您应该显示通用错误消息(或实现Exception.getUserFriendlyMessage()),并记录错误(如果可能,请提供日志ID)。


1
我想向用户显示的字符串是“错误描述在这里”。
您可以使用Exception.getMessage()来实现此目的,但正如Juan Mendez建议的那样,考虑实现更“用户友好”的错误消息机制以向最终用户显示错误。
如果您不希望最终用户在堆栈跟踪中看到类、方法和字段的名称,请考虑使用像Proguard这样的混淆器。我喜欢将混淆的堆栈跟踪打印在日志文件中,然后使用ReTrace对其进行解混淆以进行调试。
有什么可能敏感的东西您不希望用户在错误堆栈中看到吗?例如,文件路径、服务器名称/IP等。
我认为这取决于抛出的异常。选择记录异常或不记录异常是应用程序安全性和有效诊断问题之间的权衡,我会根据具体情况进行处理,并留下必要的注释以解释我记录或不记录它的原因。

我更喜欢有一个名为 getUserFriendlyMessage 的方法,这样我们永远不会显示太多技术性的内容。 - Ruan Mendes
@JuanMendes 同意,实际上我在我的代码中也做了类似的事情。 - Kevin K
@JuanMendes 我不确定我是否同意。如果相同的异常可以在多种不同的上下文中抛出,您可能希望为每个上下文显示不同的错误消息。 - Michael
@Michael,所以你想让处理异常的代码根据异常类型显示消息?我认为更有意义的做法是创建异常的人通常最了解一个好的错误消息是什么。然而,有时确实需要显示特定于UI的消息,在这种情况下,按异常类型或异常代码进行选择就更合适。 - Ruan Mendes
@JuanMendes 哦,糟糕我看错了。是的,getUserFriendlyMessage返回的字符串对于每个异常实例都会不同。就像你的异常构造函数应该是 public MyException(String msg, String userFriendlyMsg) - Michael
@Michael 有时候我会结合两种方法。异常友好的消息可以让我从服务器端获取上下文,然后我在客户端添加一些上下文。一个很好的例子是当尝试保存某些内容失败时,您想向用户显示失败的原因(文档已存在),这必须来自服务器,但它只能是异常中的错误代码。然后我会在UI中添加更多解释,告诉他们如何修复它。 - Ruan Mendes

1

用户名和密码显然是敏感信息,您不希望向用户显示。此外,像您所说的那样,文件系统路径、服务器名称和IP地址也应该被隐藏。

Exception.getMessage()方法将仅返回抛出异常的消息而不是整个堆栈跟踪。但是,看起来在您给出的示例中,第二行和第三行也是异常消息的一部分,因此这对您没有帮助。

但是,向用户显示堆栈跟踪并不是良好的做法。它们不仅可能包含敏感信息,而且对于用户来说,它们的可读性与克林贡语差不多。您应该记录所有未捕获的异常,然后向用户显示更加友好的消息。


好的观点。但是如果Exception.getMessage()返回的内容中有可靠的关键词,我可以搜索并用更合适的内容替换这个消息。请参见上面原始帖子中的我的UPDATE 1。 - ggkmath

1

正如其他人所说,您不应向用户报告堆栈跟踪。有几个人建议您显示getMessage()文本。我建议不要这样做。就像我之前所说的

  • 异常抛出时创建了该消息,因此最多只能提供非常低级别的信息,这可能不适合向用户报告。
  • 从哲学上讲,使用该消息似乎违背了异常的整个目的,即将错误处理的检测和启动(throw部分)与处理完成和报告(catch部分)分开。使用该消息意味着该消息必须适合用于报告,这将责任转移到了仅应该负责检测和启动的位置。也就是说,我认为Throwable设计中的getMessage()部分是一个错误。
  • 该消息没有本地化。尽管有名为getLocalizedMessage()的方法,但它并不大有用,因为您可能直到catch异常后才知道要使用什么语言环境(报告是否要传递给由英语系统管理员读取的系统日志,还是要弹出窗口以供GUI的法国用户使用?)。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接