Java / Android - 如何打印完整的堆栈跟踪?

143
在Android(Java)中,如何打印完整的堆栈跟踪?如果我的应用程序由于空指针异常或其他原因崩溃,它会打印出一个(几乎)完整的堆栈跟踪,如下所示:
java.io.IOException: Attempted read from closed stream.
com.android.music.sync.common.SoftSyncException: java.io.IOException: Attempted read from closed stream.
    at com.android.music.sync.google.MusicSyncAdapter.getChangesFromServerAsDom(MusicSyncAdapter.java:545)
    at com.android.music.sync.google.MusicSyncAdapter.fetchDataFromServer(MusicSyncAdapter.java:488)
    at com.android.music.sync.common.AbstractSyncAdapter.download(AbstractSyncAdapter.java:417)
    at com.android.music.sync.common.AbstractSyncAdapter.innerPerformSync(AbstractSyncAdapter.java:313)
    at com.android.music.sync.common.AbstractSyncAdapter.onPerformLoggedSync(AbstractSyncAdapter.java:243)
    at com.google.android.common.LoggingThreadedSyncAdapter.onPerformSync(LoggingThreadedSyncAdapter.java:33)
    at android.content.AbstractThreadedSyncAdapter$SyncThread.run(AbstractThreadedSyncAdapter.java:164)
Caused by: java.io.IOException: Attempted read from closed stream.
    at org.apache.http.impl.io.ChunkedInputStream.read(ChunkedInputStream.java:148)
    at org.apache.http.conn.EofSensorInputStream.read(EofSensorInputStream.java:159)
    at java.util.zip.GZIPInputStream.readFully(GZIPInputStream.java:212)
    at java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:81)
    at java.util.zip.GZIPInputStream.<init>(GZIPInputStream.java:64)
    at android.net.http.AndroidHttpClient.getUngzippedContent(AndroidHttpClient.java:218)
    at com.android.music.sync.api.MusicApiClientImpl.createAndExecuteMethod(MusicApiClientImpl.java:312)
    at com.android.music.sync.api.MusicApiClientImpl.getItems(MusicApiClientImpl.java:588)
    at com.android.music.sync.api.MusicApiClientImpl.getTracks(MusicApiClientImpl.java:638)
    at com.android.music.sync.google.MusicSyncAdapter.getChangesFromServerAsDom(MusicSyncAdapter.java:512)
    ... 6 more

然而,有时候出于调试目的,我希望能够记录下代码所在的完整堆栈跟踪。我想我可以这样做:

StackTraceElement trace = new Exception().getStackTrace();
Log.d("myapp", trace.toString());

但是这只是打印对象的指针...我需要遍历所有堆栈跟踪元素才能将它们全部打印出来吗?还是有一种简单的方法可以将其全部打印出来?


1
你可以使用 Thread.currentThread().getStackTrace() 方法。参考链接:https://dev59.com/jHNA5IYBdhLWcg3wKacx - user2024270
1
可能是Android - 将完整的异常回溯打印到日志的重复问题。 - TT--
9个回答

186
以下代码应该可以解决问题:
Log.d("myapp", Log.getStackTraceString(new Exception()));

请注意,...x more在结尾处并没有截断堆栈跟踪信息:

(这表示)此异常的剩余堆栈跟踪与由此异常引起的异常(“封装”异常)的堆栈跟踪底部的指定帧数相匹配。

换句话说,用第一个异常的最后x行替换x more


1
getStackTraceString() 方法是从哪里来的?在 Eclipse 中找不到它?它不是 ThrowableException 的一部分... - Jake Wilson
9
“...6 more”并没有删减任何信息,它告诉你,剩下的堆栈跟踪与顶部异常相同。只需查看第一个异常的最后6行即可。 - Tomasz
1
你可能需要导入日志记录库(使用import android.util.Log;)。 - Dan

128

所有日志方法都有使用带有 (String tag, String msg, Throwable tr) 签名的覆盖。

将异常作为第三个参数传递应该可以在logcat中显示完整的堆栈跟踪信息。


5
FYI,三个参数的日志方法在幕后使用了由@Thomas提到的getStackTraceString()方法。 - Philipp Reichart
6
我已经开发安卓应用两年了,之前从未注意到这一点。非常感谢你。 - Anh Tuan
看了源代码,是的,@PhilippReichart 你说得对。这是 AOSP 4.2.2_r1 上 Log.d 的代码。 - Ehtesh Choudhury
Log.e和Log.d不起作用 :( - blinker

11

Log.getStackTraceString() 不会记录堆栈跟踪,它只是将其作为字符串返回。上述代码不会记录任何内容,并且如果 e.getCause()null,还会导致 NPE 错误。 - Philipp Reichart

10
private static String buildStackTraceString(final StackTraceElement[] elements) {
    StringBuilder sb = new StringBuilder();
    if (elements != null && elements.length > 0) {
        for (StackTraceElement element : elements) {
            sb.append(element.toString());
        }
    }
    return sb.toString();
}


// call this at your check point
Log.d(TAG, buildStackTraceString(Thread.currentThread().getStackTrace()));

7
您可以使用Thread.dumpStack()等方法,在应用程序代码中的任何位置打印堆栈跟踪。
请查看此链接以获取更多详细信息。

6
您可以使用这个:
public static String toString(StackTraceElement[] stackTraceElements) {
    if (stackTraceElements == null)
        return "";
    StringBuilder stringBuilder = new StringBuilder();
    for (StackTraceElement element : stackTraceElements)
        stringBuilder.append(element.toString()).append("\n");
    return stringBuilder.toString();
}

2
您需要使用Throwable对象来获取完整的堆栈跟踪。
try{
 // code here
}catch(Exception e){
    String exception = getStackTrace(e);
}

public static String getStackTrace(final Throwable throwable) {
     final StringWriter sw = new StringWriter();
     final PrintWriter pw = new PrintWriter(sw, true);
     throwable.printStackTrace(pw);
     return sw.getBuffer().toString();
}

Ref: https://dev59.com/QmMl5IYBdhLWcg3wP1LE#18546861


0
对于Kotlin Android用户来说,我们不需要创建扩展函数来循环遍历整个堆栈消息,因为Kotlin的throwable类中已经内置了此功能。
这就是Kotlin v>1.4的底层实现。
@SinceKotlin("1.4")
public actual fun Throwable.stackTraceToString(): String {
    val sw = StringWriter()
    val pw = PrintWriter(sw)
    printStackTrace(pw)
    pw.flush()
    return sw.toString()
} 

用法:

 Log.d("ERROR_STACKTRACE","${t.stackTraceToString()}") // 't' is the throwable obj

0

我现在快速编写了一个递归函数,它将迭代throwable和throwable.getCause()。

这是因为每个"throwable.getCause()"都会返回一个带有一些重复行和新行的新异常消息。 所以概念是:如果有一个"cause",那么主要的throwable上就会有一行带有"n more..",所以我获取在带有"n more.."之前的最后一行,然后获取cause消息,最后我对cause消息进行子字符串处理,只获取最后一个重复行之后的部分(即出现在主要throwable和cause throwable上的最后一行)。

然后当我获取cause消息时,我使用递归,因此重新调用相同的函数以从主要throwable中获取cause消息,我将获得已替换的消息。如果主要throwable的cause throwable还有另一个cause,那么主要throwable就有3个级别(main -> cause -> cause-of-cause),在主要throwable上,我将获得一个已经替换的消息(使用与主要相同的概念)。

public static <T extends Throwable> String printStackTraceString(T ex) {     // Recursive
    Throwable tr = ex;
    if (tr != null) {
        String st = Log.getStackTraceString(tr);
        if (tr.getCause() != null) {
            // Recursion...
            String cs = printStackTraceString(tr.getCause());

            String r1 = st.subSequence(0x0, st.lastIndexOf("\n", st.lastIndexOf("\n") - "\n".length())).toString();
            String replace = r1.substring(r1.lastIndexOf("\n"));
            if (cs.contains(replace)) {
                return r1.concat(cs.subSequence(cs.indexOf(replace) + replace.length(), cs.length()).toString());
            }
        }
        return st;
    }
    return "";
}

我只尝试了两个级别(主要 -> 原因),而不是更多的级别 :/ 如果有任何问题,请编辑函数并写下评论:D

祝你编码愉快,也祝你有美好的一天:D

重要提示:

如果“st”不包含“\n”或类似字符(我发现某些异常堆栈跟踪存在此问题),则此代码有时会出现异常。 为解决此问题,您需要在代码行“String r1 = ...”之前添加一些检查。

您需要检查:“st”是否包含“\n”,以及“st.subSequence”的起始和结束索引是否都有效。

无论如何,我建议将其放在try-catch中,并在出现异常时返回空字符串。(它是递归的,因此返回的空字符串将与先前处理过的字符串连接在一起)。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接