然而,我很难找到任何一个合理的原因,为什么打印堆栈跟踪是不良做法,因为堆栈跟踪在追踪引起异常的原因方面非常有用。我知道的一些事情包括:此检查可用于[...]查找常见的不良实践,如调用ex.printStacktrace()
以下是避免在代码中打印堆栈跟踪的其他原因:
1. 堆栈跟踪可能包含敏感信息,如文件路径、用户名和密码等。
2. 堆栈跟踪可能会向攻击者提供有关您的应用程序的详细信息,从而增加了安全风险。
3. 堆栈跟踪可能会使代码更难以阅读和理解,特别是对于新手来说。
然而,我很难找到任何一个合理的原因,为什么打印堆栈跟踪是不良做法,因为堆栈跟踪在追踪引起异常的原因方面非常有用。我知道的一些事情包括:此检查可用于[...]查找常见的不良实践,如调用ex.printStacktrace()
Throwable.printStackTrace()
方法将堆栈跟踪输出到System.err
的PrintStream。JVM进程的System.err
流和其基础标准的“错误”输出流可以通过以下方式进行重定向:
System.setErr()
,它将更改System.err
指向的目标。/dev/null
所示。从上面推断出,调用Throwable.printStackTrace()
构成有效(不是好/很好)的异常处理行为,仅当:
System.err
,System.err
(以及JVM的标准错误输出流)。在大多数情况下,上述条件不满足。一个人可能不知道JVM中运行的其他代码,并且无法预测日志文件的大小或进程的运行时间,而良好设计的记录实践将围绕编写“可机器解析”的日志文件(日志记录器中的可选特性),并将其写入已知的目标位置,以帮助支持。
最后,需要记住Throwable.printStackTrace()
的输出一定会与其他写入System.err
(如果两者都被重定向到相同的文件/设备,则可能包括System.out
)的内容交错。这对于单线程应用程序来说是一个麻烦,因为在这种情况下,异常周围的数据不易被解析。更糟糕的是,多线程应用程序很可能会产生非常混乱的日志,因为Throwable.printStackTrace()
不是线程安全的。
当多个线程同时调用Throwable.printStackTrace()
时,没有同步机制来同步堆栈跟踪写入到System.err
。要解决这个问题,实际上需要在与System.err
相关联的监视器上同步代码(如果目标文件/设备相同,则还需要同步System.out
),而这对于日志文件的正常性来说是一个相当沉重的代价。例如,ConsoleHandler
和StreamHandler
类负责将日志记录追加到控制台,在由java.util.logging
提供的日志记录工具中,发布日志记录的实际操作是同步的-每个尝试发布日志记录的线程也必须获取与StreamHandler
实例关联的监视器上的锁定。如果想要使用System.out
/System.err
记录非交错的日志记录,必须确保以可串行化的方式将消息发布到这些流中。
考虑到以上所有内容以及Throwable.printStackTrace()
实际上有很受限制的场景,通常情况下调用它是不好的做法。
延伸前面段落的论点,使用Throwable.printStackTrace
配合写入到控制台的日志记录器也是一个不好的选择。部分原因是,日志记录器会在不同的监视器上同步,而您的应用程序(如果不想要交错的日志记录)将在不同的监视器上同步。如果在应用程序中使用写入到同一目的地的两个不同的日志记录器,该论点仍然适用。
System.out.println
和 Throwable.printStackTrace
,当然,需要开发者自己判断。我有点担心线程安全的部分被忽略了。如果你看大多数日志记录器的实现,你会注意到它们同步写入日志记录的部分(甚至是控制台),尽管它们没有获取 System.err
或 System.out
的监视器。 - Vineet Reynolds1)堆栈跟踪信息不应该对终端用户可见(出于用户体验和安全考虑)。
确实,它应该可以访问以诊断终端用户的问题,但出于两个原因,终端用户不应该看到它们:
2)生成堆栈跟踪信息是一个相对昂贵的过程(虽然在大多数“异常”情况下不太可能成为问题)
生成堆栈跟踪信息发生在创建/抛出异常时(这就是为什么抛出异常需要一定代价的原因),但打印并不那么昂贵。事实上,你可以用自定义异常重写 Throwable#fillInStackTrace()
方法,这样就可以让抛出异常的代价几乎和简单的 GOTO 语句一样小。
3)很多日志框架会为你打印堆栈跟踪信息(我们的框架不会,并且不容易更改)
非常好的观点。主要问题在于:如果框架为你记录了异常,什么都不要做(但一定要确保它这样做!)。如果你想自己记录异常,请使用像 Logback 或者 Log4J 这样的日志框架,而不是直接输出到控制台,因为很难控制控制台的输出。
使用日志框架,你可以轻松地将堆栈跟踪信息重定向到文件、控制台甚至发送到指定的电子邮件地址。而对于硬编码的 printStackTrace()
,你只能接受标准输出流sysout
的形式。
再强调一遍:正确记录4)打印堆栈跟踪信息并不等同于错误处理。它应该与其他信息记录和异常处理一起使用。
SQLException
(使用日志框架记录完整的堆栈跟踪),并显示友好的信息:“抱歉,我们目前无法处理您的请求”。你真的认为用户对原因感兴趣吗?你看过StackOverflow错误界面吗?它很幽默,但不会透露任何细节。但是它确保了用户问题将得到调查。printStackTrace()方法并不像你所说的那样代价高昂,因为堆栈跟踪是在异常创建时填充的。
关键是通过日志记录器框架传递任何进入日志的内容,以便控制日志记录。因此,不要使用printStackTrace,而应该使用类似于Logger.log(msg, exception);
的内容。
在异常处理中打印堆栈跟踪并不构成不良实践,但是仅在出现异常时打印堆栈跟踪可能是问题所在——通常情况下,仅Printing the exception's stack trace is not enough。
此外,在catch
块中仅执行e.printStackTrace
可能会让人怀疑是否正在执行适当的异常处理。不适当的处理最好情况下意味着问题被忽略,最坏情况下可能会导致程序继续以未定义或意外的状态执行。
示例
让我们考虑以下示例:
try {
initializeState();
} catch (TheSkyIsFallingEndOfTheWorldException e) {
e.printStackTrace();
}
continueProcessingAssumingThatTheStateIsCorrect();
这里,我们希望在继续进行需要初始化完成的处理之前做一些初始化处理。
在上面的代码中,异常应该被捕获并适当处理,以防程序继续执行到假定会引起问题的continueProcessingAssumingThatTheStateIsCorrect
方法。
在许多情况下,e.printStackTrace()
表明某些异常被吞没了,允许处理继续进行,就好像没有发生任何问题一样。
为什么这成为一个问题?
可能最大的原因之一是IDE(例如Eclipse)会自动生成代码来处理异常,其中包括e.printStackTrace
。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
(上面的try-catch
是Eclipse自动生成的代码,用于处理由Thread.sleep
引发的InterruptedException
。)
对于大多数应用程序而言,仅将堆栈跟踪打印到标准错误可能是不够的。不当的异常处理可能会导致应用程序运行在意料之外的状态下,并可能导致意料之外和未定义的行为。
try {
// do stuff
} catch (Exception e) {
e.printStackTrace(); // and swallow the exception
}
printStackTrace
调用组成:异常没有得到适当处理,也没有被允许逃逸。printStackTrace()
会在控制台上输出信息。在生产环境中,没有人会一直查看控制台。Suraj是正确的,应该将这些信息传递给记录器。
catch
块中调用e.printStackTrace()
,它不会停止线程执行,并且将继续进行正常情况下的try块之后。RuntimeException
,或者将异常传递给调用者,以避免静默崩溃(例如,由于日志记录器配置不当)。
.printStackTrace()
:) - dimo414e.printStackTrace(System.out);
- Traeyee