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

发布于 2024-12-05 10:19:27 字数 667 浏览 2 评论 0 原文

有很多材料那里表明打印异常的堆栈跟踪是不好的做法。

例如,来自 Checkstyle 中的 RegexpSingleline 检查:

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

但是,我正在努力寻找给出有效理由的任何地方,因为堆栈跟踪对于追踪什么非常有用导致异常。我知道的事情:

  1. 最终用户永远不应该看到堆栈跟踪(出于用户体验和安全目的)

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

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

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

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

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:

  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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(9

云朵有点甜 2024-12-12 10:19:27

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 to System.err PrintStream. The System.err stream and the underlying standard "error" output stream of the JVM process can be redirected by

  • invoking System.setErr() which changes the destination pointed to by 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.

Inferring from the above, invoking Throwable.printStackTrace() constitutes valid (not good/great) exception handling behavior, only

  • if you do not have System.err being reassigned throughout the duration of the application's lifetime,
  • 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).

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 to System.err (and possibly even System.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 as Throwable.printStackTrace() is not thread-safe.

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. 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. 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; 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. 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.

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.

皇甫轩 2024-12-12 10:19:27

您在这里触及多个问题:

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

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

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

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

生成堆栈跟踪在创建/抛出异常时发生(这就是抛出异常需要付出代价的原因) ,印刷没那么贵。事实上,您可以在自定义异常中有效地重写 Throwable#fillInStackTrace() ,从而使抛出异常几乎与简单的 GOTO 语句一样便宜。

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

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

使用日志记录框架,您可以轻松地将堆栈跟踪重定向到文件、控制台,甚至将它们发送到指定的电子邮件地址。使用硬编码的printStackTrace(),您必须忍受sysout

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

再次:正确记录SQLException(使用完整的堆栈跟踪,使用日志框架)并显示良好的:“抱歉,我们当前无法处理您的请求”消息。你真的认为用户感兴趣的原因吗?您见过 StackOverflow 错误屏幕吗?这非常幽默,但没有透露任何细节。然而,它可以确保用户问题将得到调查。

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


总结一下:始终记录异常(最好使用日志框架),但不要将它们暴露给最终用户。仔细考虑 GUI 中的错误消息,仅在开发模式下显示堆栈跟踪。

You are touching multiple issues here:

1) A stack trace should never be visibile to end users (for user experience and security purposes)

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?

2) Generating a stack trace is a relatively expensive process (though unlikely to be an issue in most 'exception'al circumstances)

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.

3) Many logging frameworks will print the stack trace for you (ours does not and no, we can't change it easily)

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 the sysout.

4) Printing the stack trace does not constitute error handling. 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. 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.

忆梦 2024-12-12 10:19:27

首先,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);

岁月无声 2024-12-12 10:19:27

打印异常的堆栈跟踪本身并不构成不良实践,但在异常发生时打印 stace 跟踪可能是这里的问题 - 通常,仅打印堆栈跟踪是不够的。

此外,如果 catch 块中执行的所有操作都是 e.printStackTrace,则可能会怀疑未执行正确的异常处理。处理不当最多可能意味着问题被忽略,最坏的情况可能意味着程序在未定义或意外的状态下继续执行。

示例

让我们考虑以下示例:

try {
  initializeState();

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

continueProcessingAssumingThatTheStateIsCorrect();

在这里,我们想要在继续​​进行一些需要初始化的处理之前进行一些初始化处理。

在上面的代码中,应该捕获并正确处理异常,以防止程序继续执行我们认为会导致问题的 continueProcessingAssumingThatTheStateIsCorrect 方法。

在许多情况下,e.printStackTrace() 表明某些异常正在被吞掉,并且允许处理继续进行,就好像没有发生任何问题一样。

为什么这会成为一个问题?

糟糕的异常处理变得更加普遍的最大原因之一可能是 Eclipse 等 IDE 如何自动生成执行 e 的代码。 printStackTrace 用于异常处理:(

try {
  Thread.sleep(1000);
} catch (InterruptedException e) {
  // TODO Auto-generated catch block
  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 a 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.

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:

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.)

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.

长梦不多时 2024-12-12 10:19:27

我认为你列出的理由非常全面。

我不止一次遇到的一个特别糟糕的例子是这样的:

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

上面代码的问题是处理完全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:

    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.

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.

落日海湾 2024-12-12 10:19:27

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.

清引 2024-12-12 10:19:27

这并不是坏习惯,因为 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.

仙女山的月亮 2024-12-12 10:19:27

在服务器应用程序中,堆栈跟踪会破坏您的 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

说好的呢 2024-12-12 10:19:27

正如一些人已经在这里提到的,问题在于如果您只是在 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 the catch 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).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文