有很多材料那里表明打印异常的堆栈跟踪是不好的做法。
例如,来自 Checkstyle 中的 RegexpSingleline 检查:
此检查可用于发现常见的不良做法,例如调用 ex.printStacktrace()
但是,我正在努力寻找给出有效理由的任何地方,因为堆栈跟踪对于追踪什么非常有用导致异常。我知道的事情:
-
最终用户永远不应该看到堆栈跟踪(出于用户体验和安全目的)
-
生成堆栈跟踪是一个相对昂贵的过程(尽管在大多数“例外”情况下不太可能成为问题)
-
许多日志记录框架将为您打印堆栈跟踪(我们的不会,不,我们不能轻易更改它) )
-
打印堆栈跟踪并不构成错误处理。它应该与其他信息记录和异常处理结合起来。
避免在代码中打印堆栈跟踪还有哪些其他原因?
There is a lot of material out there which suggests that printing the stack trace of an exception is bad practice.
E.g. from the RegexpSingleline check in Checkstyle:
This check can be used [...] to find common bad practice such as calling ex.printStacktrace()
However, I'm struggling to find anywhere which gives a valid reason why since surely the stack trace is very useful in tracking down what caused the exception. Things that I am aware of:
-
A stack trace should never be visible to end users (for user experience and security purposes)
-
Generating a stack trace is a relatively expensive process (though unlikely to be an issue in most 'exceptional' circumstances)
-
Many logging frameworks will print the stack trace for you (ours does not and no, we can't change it easily)
-
Printing the stack trace does not constitute error handling. It should be combined with other information logging and exception handling.
What other reasons are there for avoiding printing a stack trace in your code?
发布评论
评论(9)
Throwable.printStackTrace()
将堆栈跟踪写入System.err
PrintStream。 System.err 流和 JVM 进程的底层标准“错误”输出流可以通过System.setErr()
更改指向的目标系统错误
。/dev/null
的情况。调用
Throwable.printStackTrace()
才构成有效(不好/很好)的异常处理行为。System.err
时, 应用程序生命周期的持续时间,System.err
(以及 JVM 的标准错误输出流)。大多数情况下,上述条件并不满足。人们可能不知道 JVM 中运行的其他代码,并且无法预测日志文件的大小或进程的运行时持续时间,并且设计良好的日志记录实践将围绕编写“机器可解析”日志文件(a记录器中的优选但可选功能)在已知目的地,以帮助支持。
最后,我们应该记住,Throwable.printStackTrace() 的输出肯定会与写入 System.err 的其他内容(甚至可能是 System.err)交织在一起。 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 与写入控制台的记录器结合使用也是一个糟糕的选择。这在一定程度上是由于记录器将在不同的监视器上同步,而您的应用程序将(可能,如果您不希望交错的日志记录)在不同的监视器上同步。当您在应用程序中使用两个不同的记录器写入同一目标时,该论点也成立。
Throwable.printStackTrace()
writes the stack trace toSystem.err
PrintStream. TheSystem.err
stream and the underlying standard "error" output stream of the JVM process can be redirected bySystem.setErr()
which changes the destination pointed to bySystem.err
./dev/null
.Inferring from the above, invoking
Throwable.printStackTrace()
constitutes valid (not good/great) exception handling behavior, onlySystem.err
being reassigned throughout the duration of the application's lifetime,System.err
(and the JVM's standard error output stream).In most cases, the above conditions are not satisfied. One may not be aware of other code running in the JVM, and one cannot predict the size of the log file or the runtime duration of the process, and a well designed logging practice would revolve around writing "machine-parseable" log files (a preferable but optional feature in a logger) in a known destination, to aid in support.
Finally, one ought to remember that the output of
Throwable.printStackTrace()
would definitely get interleaved with other content written toSystem.err
(and possibly evenSystem.out
if both are redirected to the same file/device). This is an annoyance (for single-threaded apps) that one must deal with, for the data around exceptions is not easily parseable in such an event. Worse, it is highly likely that a multi-threaded application will produce very confusing logs asThrowable.printStackTrace()
is not thread-safe.There is no synchronization mechanism to synchronize the writing of the stack trace to
System.err
when multiple threads invokeThrowable.printStackTrace()
at the same time. Resolving this actually requires your code to synchronize on the monitor associated withSystem.err
(and alsoSystem.out
, if the destination file/device is the same), and that is rather heavy price to pay for log file sanity. To take an example, theConsoleHandler
andStreamHandler
classes are responsible for appending log records to console, in the logging facility provided byjava.util.logging
; the actual operation of publishing log records is synchronized - every thread that attempts to publish a log record must also acquire the lock on the monitor associated with theStreamHandler
instance. If you wish to have the same guarantee of having non-interleaved log records usingSystem.out
/System.err
, you must ensure the same - the messages are published to these streams in a serializable manner.Considering all of the above, and the very restricted scenarios in which
Throwable.printStackTrace()
is actually useful, it often turns out that invoking it is a bad practice.Extending the argument in the one of the previous paragraphs, it is also a poor choice to use
Throwable.printStackTrace
in conjunction with a logger that writes to the console. This is in part, due to the reason that the logger would synchronize on a different monitor, while your application would (possibly, if you don't want interleaved log records) synchronize on a different monitor. The argument also holds good when you use two different loggers that write to the same destination, in your application.您在这里触及多个问题:
是的,应该可以访问它来诊断最终用户的问题,但最终用户不应该看到它们,原因有两个:
生成堆栈跟踪在创建/抛出异常时发生(这就是抛出异常需要付出代价的原因) ,印刷没那么贵。事实上,您可以在自定义异常中有效地重写 Throwable#fillInStackTrace() ,从而使抛出异常几乎与简单的 GOTO 语句一样便宜。
非常好的一点。这里的主要问题是:如果框架为您记录异常,则不执行任何操作(但确保它确实执行!)如果您想自己记录异常,请使用日志记录框架,例如 Logback 或 Log4J,不要将它们放在上面原始控制台,因为它很难控制。
使用日志记录框架,您可以轻松地将堆栈跟踪重定向到文件、控制台,甚至将它们发送到指定的电子邮件地址。使用硬编码的
printStackTrace()
,您必须忍受sysout
。再次:正确记录
SQLException
(使用完整的堆栈跟踪,使用日志框架)并显示良好的:“抱歉,我们当前无法处理您的请求”消息。你真的认为用户感兴趣的原因吗?您见过 StackOverflow 错误屏幕吗?这非常幽默,但没有透露任何细节。然而,它可以确保用户问题将得到调查。但他会立即给您打电话,您需要能够诊断问题。因此,您需要两者:正确的异常日志记录和用户友好的消息。
总结一下:始终记录异常(最好使用日志框架),但不要将它们暴露给最终用户。仔细考虑 GUI 中的错误消息,仅在开发模式下显示堆栈跟踪。
You are touching multiple issues here:
Yes, it should be accessible to diagnose problems of end-users, but end-user should not see them for two reasons:
Generating a stack trace happens when the exception is being created/thrown (that's why throwing an exception comes with a price), printing is not that expensive. In fact you can override
Throwable#fillInStackTrace()
in your custom exception effectively making throwing an exception almost as cheap as a simple GOTO statement.Very good point. The main issue here is: if the framework logs the exception for you, do nothing (but make sure it does!) If you want to log the exception yourself, use logging framework like Logback or Log4J, to not put them on the raw console because it is very hard to control it.
With logging framework you can easily redirect stack traces to file, console or even send them to a specified e-mail address. With hardcoded
printStackTrace()
you have to live with thesysout
.Again: log
SQLException
correctly (with the full stack trace, using logging framework) and show nice: "Sorry, we are currently not able to process your request" message. Do you really think the user is interested in the reasons? Have you seen StackOverflow error screen? It's very humorous, but does not reveal any details. However it ensures the user that the problem will be investigated.But he will call you immediately and you need to be able to diagnose the problem. So you need both: proper exception logging and user-friendly messages.
To wrap things up: always log exceptions (preferably using logging framework), but do not expose them to the end-user. Think carefully and about error-messages in your GUI, show stack traces only in development mode.
首先,printStackTrace() 正如您所说,并不昂贵,因为堆栈跟踪是在异常本身创建时填充的。
这个想法是通过记录器框架传递任何进入日志的内容,以便可以控制日志记录。因此,不要使用 printStackTrace,只需使用 Logger.log(msg, exception); 之类的东西即可
First thing printStackTrace() is not expensive as you state, because the stack trace is filled when the exception is created itself.
The idea is to pass anything that goes to logs through a logger framework, so that the logging can be controlled. Hence instead of using printStackTrace, just use something like
Logger.log(msg, exception);
打印异常的堆栈跟踪本身并不构成不良实践,但仅在异常发生时打印 stace 跟踪可能是这里的问题 - 通常,仅打印堆栈跟踪是不够的。
此外,如果
catch
块中执行的所有操作都是e.printStackTrace
,则可能会怀疑未执行正确的异常处理。处理不当最多可能意味着问题被忽略,最坏的情况可能意味着程序在未定义或意外的状态下继续执行。示例
让我们考虑以下示例:
在这里,我们想要在继续进行一些需要初始化的处理之前进行一些初始化处理。
在上面的代码中,应该捕获并正确处理异常,以防止程序继续执行我们认为会导致问题的
continueProcessingAssumingThatTheStateIsCorrect
方法。在许多情况下,
e.printStackTrace()
表明某些异常正在被吞掉,并且允许处理继续进行,就好像没有发生任何问题一样。为什么这会成为一个问题?
糟糕的异常处理变得更加普遍的最大原因之一可能是 Eclipse 等 IDE 如何自动生成执行
e 的代码。 printStackTrace
用于异常处理:(上面是 Eclipse 自动生成的实际
try-catch
,用于处理由Thread.sleep 抛出的
。)InterruptedException
对于对于大多数应用程序,仅将堆栈跟踪打印到标准错误可能是不够的。在许多情况下,不正确的异常处理可能会导致应用程序在意外状态下运行,并可能导致意外和未定义的行为。
Printing the exception's stack trace in itself doesn't constitute bad practice, but only printing the stace trace when an exception occurs is probably the issue here -- often times, just printing a stack trace is not enough.
Also, there's a tendency to suspect that proper exception handling is not being performed if all that is being performed in a
catch
block is ae.printStackTrace
. Improper handling could mean at best an problem is being ignored, and at worst a program that continues executing in an undefined or unexpected state.Example
Let's consider the following example:
Here, we want to do some initialization processing before we continue on to some processing that requires that the initialization had taken place.
In the above code, the exception should have been caught and properly handled to prevent the program from proceeding to the
continueProcessingAssumingThatTheStateIsCorrect
method which we could assume would cause problems.In many instances,
e.printStackTrace()
is an indication that some exception is being swallowed and processing is allowed to proceed as if no problem every occurred.Why has this become a problem?
Probably one of the biggest reason that poor exception handling has become more prevalent is due to how IDEs such as Eclipse will auto-generate code that will perform a
e.printStackTrace
for the exception handling:(The above is an actual
try-catch
auto-generated by Eclipse to handle anInterruptedException
thrown byThread.sleep
.)For most applications, just printing the stack trace to standard error is probably not going to be sufficient. Improper exception handling could in many instances lead to an application running in a state that is unexpected and could be leading to unexpected and undefined behavior.
我认为你列出的理由非常全面。
我不止一次遇到的一个特别糟糕的例子是这样的:
上面代码的问题是处理完全由
printStackTrace
调用组成:异常是确实处理不当,也不让它逃脱。另一方面,作为一项规则,每当我的代码中出现意外异常时,我总是记录堆栈跟踪。多年来,这个策略为我节省了大量的调试时间。
最后,轻松一点,上帝的完美例外。
I think your list of reasons is a pretty comprehensive one.
One particularly bad example that I've encountered more than once goes like this:
The problem with the above code is that the handling consists entirely of the
printStackTrace
call: the exception isn't really handled properly nor is it allowed to escape.On the other hand, as a rule I always log the stack trace whenever there's an unexpected exception in my code. Over the years this policy has saved me a lot of debugging time.
Finally, on a lighter note, God's Perfect Exception.
printStackTrace()
打印到控制台。在生产环境中,没有人会注意到这一点。 Suraj 是正确的,应该将此信息传递给记录器。printStackTrace()
prints to a console. In production settings, nobody is ever watching at that. Suraj is correct, should pass this information to a logger.这并不是坏习惯,因为 PrintStackTrace() 有“错误”,而是因为它有“代码味道”。
大多数情况下,PrintStackTrace() 调用是因为有人未能正确处理异常。一旦你以正确的方式处理了异常,你通常就不再关心 StackTrace 了。
此外,在 stderr 上显示堆栈跟踪通常仅在调试时有用,而在生产中则无用,因为 stderr 通常无处可去。记录它更有意义。但是,仅仅用记录异常来替换 PrintStackTrace() 仍然会让您的应用程序失败,但仍然像什么都没发生一样运行。
It is not bad practice because something is 'wrong' about PrintStackTrace(), but because it's 'code smell'.
Most of the time the PrintStackTrace() call is there because somebody failed to properly handle the exception. Once you deal with the exception in a proper way you generally don't care about the StackTrace any more.
Additionally, displaying the stacktrace on stderr is generally only useful when debugging, not in production because very often stderr goes nowhere. Logging it makes more sense. But just replacing PrintStackTrace() with logging the exception still leaves you with an application which failed but keeps running like nothing happened.
在服务器应用程序中,堆栈跟踪会破坏您的 stdout/stderr 文件。它可能会变得越来越大,并且充满无用的数据,因为通常你没有上下文,没有时间戳等等。
例如使用 tomcat 作为容器时的 catalina.out
In server applications the stacktrace blows up your stdout/stderr file. It may become larger and larger and is filled with useless data because usually you have no context and no timestamp and so on.
e.g. catalina.out when using tomcat as container
正如一些人已经在这里提到的,问题在于如果您只是在
catch
块中调用e.printStackTrace()
,则异常会被吞噬。它不会停止线程执行,并且会像正常情况一样在 try 块之后继续执行。相反,您需要尝试从异常中恢复(如果它是可恢复的),或者抛出 RuntimeException,或者将异常冒泡给调用者以避免无提示崩溃(例如,由于记录器配置不正确)。
As some guys already mentioned here the problem is with the exception swallowing in case you just call
e.printStackTrace()
in thecatch
block. It won't stop the thread execution and will continue after the try block as in normal condition.Instead of that you need either try to recover from the exception (in case it is recoverable), or to throw
RuntimeException
, or to bubble the exception to the caller in order to avoid silent crashes (for example, due to improper logger configuration).