简体   繁体   English

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

[英]Why is exception.printStackTrace() considered bad practice?

There is a lot of material out there which suggests that printing the stack trace of an exception is bad practice.有很多的材料,这表明印刷异常的堆栈跟踪是不好的做法。

Eg from the RegexpSingleline check in Checkstyle:例如,来自 Checkstyle 中的 RegexpSingleline 检查:

This check can be used [...] to find common bad practice such as calling ex.printStacktrace()此检查可用于 [...] 查找常见的不良做法,例如调用 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:我所知道的事情:

  1. A stack trace should never be visible to end users (for user experience and security purposes)堆栈跟踪永远不应对最终用户可见(出于用户体验和安全目的)

  2. Generating a stack trace is a relatively expensive process (though unlikely to be an issue in most 'exceptional' circumstances)生成堆栈跟踪是一个相对昂贵的过程(尽管在大多数“特殊”情况下不太可能成为问题)

  3. Many logging frameworks will print the stack trace for you (ours does not and no, we can't change it easily)许多日志框架会为您打印堆栈跟踪(我们的没有,也不,我们不能轻易更改它)

  4. 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?避免在代码中打印堆栈跟踪的其他原因是什么?

Throwable.printStackTrace() writes the stack trace to System.err PrintStream. Throwable.printStackTrace()将堆栈跟踪写入System.err PrintStream。 The System.err stream and the underlying standard "error" output stream of the JVM process can be redirected by System.err流和 JVM 进程的底层标准“错误”输出流可以通过以下方式重定向

  • invoking System.setErr() which changes the destination pointed to by System.err .调用System.setErr()改变System.err指向的目的地。
  • or by redirecting the process' error output stream.或者通过重定向进程的错误输出流。 The error output stream may be redirected to a file/device错误输出流可能被重定向到文件/设备
    • whose contents may be ignored by personnel,其内容可能会被人员忽略,
    • the file/device may not be capable of log rotation, inferring that a process restart is required to close the open file/device handle, before archiving the existing contents of the file/device.文件/设备可能无法进行日志轮换,推断在归档文件/设备的现有内容之前需要重新启动进程以关闭打开的文件/设备句柄。
    • or the file/device actually discards all data written to it, as is the case of /dev/null .或者文件/设备实际上丢弃了所有写入它的数据,就像/dev/null

Inferring from the above, invoking Throwable.printStackTrace() constitutes valid (not good/great) exception handling behavior, only从上面推断,调用Throwable.printStackTrace()构成有效(不好/很好)的异常处理行为,仅

  • if you do not have System.err being reassigned throughout the duration of the application's lifetime,如果您没有在应用程序的整个生命周期内重新分配System.err
  • and if you do not require log rotation while the application is running,如果您在应用程序运行时不需要日志轮换,
  • and if accepted/designed logging practice of the application is to write to System.err (and the JVM's standard error output stream).如果接受/设计应用程序的日志记录实践是写入System.err (和 JVM 的标准错误输出流)。

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.人们可能不知道在 JVM 中运行的其他代码,并且无法预测日志文件的大小或进程的运行时长,设计良好的日志记录实践将围绕编写“机器可解析”日志文件(一种记录器中优选但可选的功能)在已知目的地,以帮助支持。

Finally, one ought to remember that the output of Throwable.printStackTrace() would definitely get interleaved with other content written to System.err (and possibly even System.out if both are redirected to the same file/device).最后,应该记住Throwable.printStackTrace()的输出肯定会与写入System.err其他内容交错(如果两者都重定向到同一个文件/设备,甚至可能是System.out )。 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 as Throwable.printStackTrace() is not thread-safe .更糟糕的是,多线程应用程序很可能会产生非常混乱的日志,因为Throwable.printStackTrace()不是线程安全的

There is no synchronization mechanism to synchronize the writing of the stack trace to System.err when multiple threads invoke Throwable.printStackTrace() at the same time.当多个线程同时调用Throwable.printStackTrace()时,没有同步机制将堆栈跟踪的写入同步到System.err Resolving this actually requires your code to synchronize on the monitor associated with System.err (and also System.out , if the destination file/device is the same), and that is rather heavy price to pay for log file sanity.解决这个问题实际上需要您的代码在与System.err相关联的监视器上同步(如果目标文件/设备相同,还需要System.out ),这对于日志文件的完整性来说是相当沉重的代价。 To take an example, the ConsoleHandler and StreamHandler classes are responsible for appending log records to console, in the logging facility provided by java.util.logging ;举个例子, ConsoleHandlerStreamHandler类负责将日志记录附加到控制台,在java.util.logging提供的日志工具java.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 the StreamHandler instance.发布日志记录的实际操作是同步的——每个尝试发布日志记录的线程还必须获取与StreamHandler实例关联的监视器上的锁。 If you wish to have the same guarantee of having non-interleaved log records using System.out / System.err , you must ensure the same - the messages are published to these streams in a serializable manner.如果您希望使用System.out / System.err获得非交错日志记录的相同保证,则必须确保相同 - 消息以可序列化的方式发布到这些流。

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.考虑到上述所有情况,以及Throwable.printStackTrace()实际上有用的非常有限的场景,通常证明调用它是一种不好的做法。


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.扩展前面一段中的论点,将Throwable.printStackTrace与写入控制台的记录器结合使用也是一个糟糕的选择。 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.当您在应用程序中使用两个不同的记录器写入同一目的地时,该论点也适用。

You are touching multiple issues here:您在这里涉及多个问题:

1) A stack trace should never be visibile to end users (for user experience and security purposes) 1) 堆栈跟踪永远不应该对最终用户可见(出于用户体验和安全目的)

Yes, it should be accessible to diagnose problems of end-users, but end-user should not see them for two reasons:是的,它应该可以用于诊断最终用户的问题,但最终用户不应该看到它们,原因有两个:

  • They are very obscure and unreadable, the application will look very user-unfriendly.它们非常晦涩难懂,应用程序看起来非常不友好。
  • Showing a stack trace to end-user might introduce a potential security risk.向最终用户显示堆栈跟踪可能会带来潜在的安全风险。 Correct me if I'm wrong, PHP actually prints function parameters in stack trace - brilliant, but very dangerous - if you would you get exception while connecting to the database, what are you likely to in the stacktrace?如果我错了,请纠正我,PHP 实际上在堆栈跟踪中打印函数参数 - 很棒,但非常危险 - 如果您在连接到数据库时遇到异常,您可能会在堆栈跟踪中做什么?

2) Generating a stack trace is a relatively expensive process (though unlikely to be an issue in most 'exception'al circumstances) 2)生成堆栈跟踪是一个相对昂贵的过程(尽管在大多数“例外”情况下不太可能成为问题)

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.事实上,您可以在自定义异常中覆盖Throwable#fillInStackTrace() ,从而有效地使抛出异常几乎与简单的 GOTO 语句一样便宜。

3) Many logging frameworks will print the stack trace for you (ours does not and no, we can't change it easily) 3)许多日志框架会为您打印堆栈跟踪(我们没有,也没有,我们不能轻易更改)

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.这里的主要问题是:如果框架为你记录异常,什么都不做(但要确保它记录了!)如果你想自己记录异常,使用LogbackLog4J 之类的日志框架,不要把它们放在原始控制台上因为它很难控制。

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 the sysout .使用硬编码的printStackTrace()你必须忍受sysout

4) Printing the stack trace does not constitute error handling. 4) 打印堆栈跟踪不构成错误处理。 It should be combined with other information logging and exception handling.它应该与其他信息记录和异常处理相结合。

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.再次:正确记录SQLException (使用完整的堆栈跟踪,使用日志框架)并显示:“抱歉,我们目前无法处理您的请求”消息。 Do you really think the user is interested in the reasons?你真的认为用户感兴趣的原因是什么? Have you seen StackOverflow error screen?你见过 StackOverflow 错误屏幕吗? 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.仔细考虑 GUI 中的错误消息,仅在开发模式下显示堆栈跟踪。

First thing printStackTrace() is not expensive as you state, because the stack trace is filled when the exception is created itself.第一件事printStackTrace()正如您所说的那样并不昂贵,因为在创建异常本身时会填充堆栈跟踪。

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);因此,不要使用 printStackTrace,只需使用类似Logger.log(msg, exception);东西Logger.log(msg, exception);

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.打印异常的堆栈跟踪本身并不构成不良做法,但在发生异常时打印 stace 跟踪可能是这里的问题——通常,仅打印堆栈跟踪是不够的。

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 a e.printStackTrace .此外,如果在catch块中执行的所有操作都是e.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:让我们考虑以下示例:

try {
  initializeState();

} catch (TheSkyIsFallingEndOfTheWorldException e) {
  e.printStackTrace();
}

continueProcessingAssumingThatTheStateIsCorrect();

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.在上面的代码中,异常应该被捕获并正确处理,以防止程序continueProcessingAssumingThatTheStateIsCorrect我们可以假设会导致问题的continueProcessingAssumingThatTheStateIsCorrect方法。

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.在许多情况下, e.printStackTrace()表明某些异常正在被吞下,并且允许处理继续进行,就好像每次都没有发生问题一样。

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:糟糕的异常处理变得更加普遍的最大原因之一可能是 Eclipse 等 IDE 如何自动生成代码,这些代码将为异常处理执行e.printStackTrace

try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  // TODO Auto-generated catch block
  e.printStackTrace();
}

(The above is an actual try-catch auto-generated by Eclipse to handle an InterruptedException thrown by Thread.sleep .) (以上是 Eclipse 自动生成的实际try-catch ,用于处理Thread.sleep抛出的InterruptedException 。)

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.在许多情况下,不正确的异常处理可能会导致应用程序在意外状态下运行,并可能导致意外和未定义的行为。

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:我不止一次遇到过的一个特别糟糕的例子是这样的:

    try {
      // do stuff
    } catch (Exception e) {
        e.printStackTrace(); // and swallow the exception
    }

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.上面代码的问题在于处理完全printStackTrace调用组成:异常没有真正得到正确处理,也不允许逃逸。

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() prints to a console. printStackTrace()打印到控制台。 In production settings, nobody is ever watching at that.在生产环境中,没有人会注意到这一点。 Suraj is correct, should pass this information to a logger. Suraj 是正确的,应该将此信息传递给记录器。

It is not bad practice because something is 'wrong' about PrintStackTrace(), but because it's 'code smell'.这不是不好的做法,因为 PrintStackTrace() 有一些“错误”,但因为它是“代码异味”。 Most of the time the PrintStackTrace() call is there because somebody failed to properly handle the exception.大多数情况下,PrintStackTrace() 调用存在是因为有人未能正确处理异常。 Once you deal with the exception in a proper way you generally don't care about the StackTrace any more.一旦您以适当的方式处理异常,您通常不再关心 StackTrace。

Additionally, displaying the stacktrace on stderr is generally only useful when debugging, not in production because very often stderr goes nowhere.此外,在 stderr 上显示堆栈跟踪通常仅在调试时有用,而不是在生产中,因为 stderr 通常无处可去。 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.但是仅仅用记录异常来替换 PrintStackTrace() 仍然会给你留下一个失败的应用程序,但像什么也没发生一样继续运行。

In server applications the stacktrace blows up your stdout/stderr file.在服务器应用程序中,堆栈跟踪会破坏您的 stdout/stderr 文件。 It may become larger and larger and is filled with useless data because usually you have no context and no timestamp and so on.它可能会变得越来越大,并且充满了无用的数据,因为通常你没有上下文,没有时间戳等等。

eg catalina.out when using tomcat as container例如 catalina.out 使用 tomcat 作为容器时

As some guys already mentioned here the problem is with the exception swallowing in case you just call e.printStackTrace() in the catch block.正如一些人在这里已经提到的那样,问题在于吞咽异常,以防您只在catch块中调用e.printStackTrace() It won't stop the thread execution and will continue after the try block as in normal condition.它不会停止线程执行,并且会像正常情况一样在 try 块之后继续执行。

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).相反,您需要尝试从异常中恢复(如果它是可恢复的),或者抛出RuntimeException ,或者将异常冒泡给调用者以避免静默崩溃(例如,由于不正确的记录器配置)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 调用 Exception.printStackTrace 时出现 AbstractMethodError - AbstractMethodError on calling Exception.printStackTrace 记录异常和exception.printstacktrace之间的区别 - Difference between logging exception and exception.printstacktrace 如何用Exception.printStackTrace();包含时间? - How to include time with Exception.printStackTrace();? 锁定可变对象-为什么将其视为不良做法? - Locking on a mutable object - Why is it considered a bad practice? 为什么通过扩展来测试类被认为是一种不好的做法? - Why is it considered a bad practice to test a class by extending it? 为什么定义协变compareTo方法被认为是不好的做法? - Why is it considered bad practice to define a covariant compareTo method? 为什么 Hibernate Open Session in View 被认为是一种不好的做法? - Why is Hibernate Open Session in View considered a bad practice? 倒铸是否被视为不良做法? - Is down casting considered bad practice? 是否增加堆栈大小被认为是不好的做法? - Is increasing stack size considered to be a bad practice? 在fileds的setter中更新观察者被认为是不好的做法吗? - Is it considered as bad practice to update observers in fileds' setters?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM