为什么exception.printStackTrace()被认为是不好的做法?

问题

有alotofmaterialoutthere这表明打印异常的堆栈跟踪是不好的做法。

例如。来自Checkstyle的RegexpSingleline检查:

此检查可用于查找常见的不良做法,例如调用ex.printStacktrace()

但是,我很难找到任何能够提供正确理由的地方,因为堆栈跟踪在跟踪导致异常的原因时非常有用。我所知道的事情:

  • 最终用户永远不应看到堆栈跟踪(出于用户体验和安全目的)
  • 生成堆栈跟踪是一个相对昂贵的过程(尽管在大多数"特殊"情况下不太可能成为问题)
  • 许多日志记录框架将为你打印堆栈跟踪(我们的不会也不会,我们无法轻易更改它)
  • 打印堆栈跟踪不构成错误处理。它应该与其他信息记录和异常处理相结合。

还有哪些其他原因可以避免在代码中打印堆栈跟踪?


#1 热门回答(102 赞)

Throwable.printStackTrace()将堆栈跟踪写入System.errPrintStream。可以重定向System.err流和JVM进程的基础标准"错误"输出流

  • 调用System.setErr(),它更改System.err指向的目标。
  • 或通过重定向进程的错误输出流。错误输出流可能被重定向到文件/设备,其内容可能被人员忽略,文件/设备可能无法记录日志,推断在归档之前需要重新启动进程才能关闭打开的文件/设备句柄文件/设备的现有内容。或者文件/设备实际上丢弃写入它的所有数据,如/ dev / null的情况。

从上面推断,invokingThrowable.printStackTrace()构成有效(不好/好)异常处理行为,仅

  • 如果你没有在应用程序的整个生命周期内重新分配System.err,
  • 如果在应用程序运行时不需要日志轮换,
  • 如果应用程序的接受/设计日志记录实践是写入System.err(以及JVM的标准错误输出流)。

在大多数情况下,不满足上述条件。人们可能不知道在JVM中运行的其他代码,并且无法预测日志文件的大小或进程的运行时持续时间,并且精心设计的日志记录实践将围绕编写"机器可解析"日志文件(a在已知目的地中的记录器中的优选但可选的特征,以帮助支持。

最后,应该记住Throwable.printStackTrace()的输出肯定会与写入System.err的其他内容交错(并且可能甚至System.out都被重定向到同一个文件/设备)。这是一个必须处理的烦恼(对于单线程应用程序),因为异常数据在这样的事件中不容易解析。更糟糕的是,多线程应用程序极有可能产生非常混乱的日志,因为它不是线程安全的。

当多个线程同时调用Throwable.printStackTrace()时,没有同步机制来同步堆栈跟踪的写入到System.err。解决此问题实际上需要你的代码在与System.err相关联的监视器上进行同步(如果目标文件/设备相同,则还需要System.out),这对于日志文件的健全性来说是相当沉重的代价。举一个例子,ConsoleHandlerStreamHandler类负责将日志记录附加到控制台,由java.util.logging提供的日志记录工具;发布日志记录的实际操作是同步的 - 每个尝试发布日志记录的线程也必须获取与StreamHandler实例关联的监视器上的锁。如果你希望使用System.out/System.err具有相同的非交错日志记录保证,则必须确保相同 - 消息以可序列化的方式发布到这些流。

考虑到上述所有情况,以及Throwable.printStackTrace()实际上非常有用的非常有限的情况,通常会发现调用它是一种不好的做法。

扩展前面段落中的一个参数,将Throwable.printStackTrace与写入控制台的记录器结合使用也是一个不好的选择。这部分是由于记录器将在不同的监视器上同步的原因,而你的应用程序(可能,如果你不希望交错的日志记录)在不同的监视器上同步。当你在应用程序中使用两个写入同一目标的不同记录器时,该参数也很有用。


#2 热门回答(30 赞)

你在这里触及多个问题:

1)堆栈跟踪永远不应对最终用户可见(出于用户体验和安全目的)

是的,应该可以访问它来诊断最终用户的问题,但最终用户不应该看到它们有两个原因:

  • 它们非常模糊且难以理解,应用程序看起来非常不友好。
  • 向最终用户显示堆栈跟踪可能会带来潜在的安全风险。纠正我,如果我错了,PHP实际上在堆栈跟踪中打印函数参数 - 很棒,但非常危险 - 如果你在连接数据库时遇到异常,你有什么可能在堆栈跟踪中?

2)生成堆栈跟踪是一个相对昂贵的过程(尽管在大多数"异常"情况下不太可能成为问题)

在创建/抛出异常时会产生堆栈跟踪(这就是为什么抛出异常需要付出代价),打印并不昂贵。实际上,你可以在自定义异常中覆盖Throwable#fillInStackTrace(),从而使抛出异常几乎与简单的GOTO语句一样便宜。

3)许多日志记录框架将为你打印堆栈跟踪(我们的不会也不会,我们无法轻易更改它)

很好的一点。这里的主要问题是:如果框架为你记录异常,则不执行任何操作(但请确保它执行!)如果你想自己记录异常,请使用日志框架(如LogbackorLog4J),不要将它们放在原始控制台上,因为它是很难控制它。

使用日志记录框架,你可以轻松地将堆栈跟踪重定向到文件,控制台,甚至将它们发送到指定的电子邮件地址。随着hardcodedprintStackTrace()你必须与sysout一起生活。

4)打印堆栈跟踪不构成错误处理。它应该与其他信息记录和异常处理相结合。

再次:logSQLException正确(使用完整的堆栈跟踪,使用日志框架)并显示良好:"抱歉,我们目前无法处理你的请求"消息。你真的认为用户对原因感兴趣吗?你看过StackOverflow错误界面了吗?这是非常幽默,但不会揭示。但是,它确保用户将调查问题。

但是,他立即打电话给你,你需要能够诊断问题。所以你需要两个:正确的异常日志记录和用户友好的消息。

总结一下:总是log例外(最好使用266683417),但不要将它们暴露给最终用户。仔细考虑GUI中的错误消息,仅在开发模式下显示堆栈跟踪。


#3 热门回答(17 赞)

第一件事**printStackTrace()**并不像你所说的那样昂贵,因为当自己创建异常时会填充堆栈跟踪。

我们的想法是通过记录器框架传递任何记录日志,以便可以控制日志记录。因此,不要使用printStackTrace,只需使用像Logger.log(msg, exception);这样的东西