如何打印一個異常?
分兩種case吧。
1.不使用日誌框架,即簡單的systemout方式。
public static void main(String args[]) {
try {
new Main().g();
} catch (Exception e) {
System.out.println(e);
System.out.println(e.getMessage());
}
}
public void g() {
throw new RuntimeException("testEx");
}
上面是一個簡單的例子,運行之後,輸出如下:
java.lang.RuntimeException: testEx
testEx
可以看到,似乎並不是我們想要的樣子,因爲沒有堆棧信息,所以直接打印e或者e.getMessage都不是正確姿勢。
爲什麼?
直接打印e,相當於直接調用Exception的toString方法,讓我們看看其實現,是繼承自其父類中的:
public String toString() {
String s = getClass().getName();
String message = getLocalizedMessage();
return (message != null) ? (s + ": " + message) : s;
}
可以看到,僅僅是打印了異常的類名,確實沒有調用棧信息。這裏的message是一些額外的信息,是父類Throwable裏的一個成員變量,如果在構建一個Throwable實例(或子類)傳入一個string類型的message時,這個變量就會被賦值。
所以,異常類的toString方法僅僅會打印出異常的類名外加額外指定的message(如果有指定的話)。並且,e.getMessage()方法正是返回了額外的message信息。
那麼該如何打印堆棧信息呢?
繼續查看Throwable類,發現其有一個stacktrace變量以及相關的方法:
/**
* Native code saves some indication of the stack backtrace in this slot.
*/
private transient Object backtrace;
private synchronized StackTraceElement[] getOurStackTrace() {
// Initialize stack trace field with information from
// backtrace if this is the first call to this method
if (stackTrace == UNASSIGNED_STACK ||
(stackTrace == null && backtrace != null) /* Out of protocol state */) {
int depth = getStackTraceDepth();
stackTrace = new StackTraceElement[depth];
for (int i=0; i < depth; i++)
stackTrace[i] = getStackTraceElement(i);
} else if (stackTrace == null) {
return UNASSIGNED_STACK;
}
return stackTrace;
}
native StackTraceElement getStackTraceElement(int index);
看來,如果想打印異常棧,必須調用這些方法纔行。
我們可以看一下apache common庫中的工具類:
ExceptionUtils.getStackTrace(e)
結果如下:
java.lang.RuntimeException: testEx
at com.liyao.s.Main.g(Main.java:114)
at com.liyao.s.Main.main(Main.java:105)
可以看到,這次是成功打印了。
該方法的實現:
public static String getStackTrace(Throwable throwable) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, true);
throwable.printStackTrace(pw);
return sw.getBuffer().toString();
}
使用一個printwriter實例,將throwable實例的異常棧打印出來,這纔是正確打印異常的方法。
2.使用日誌框架,比如logback。
我們可以直接傳入一個throwable實例即可,不需要手動處理其調用棧,logback內部已經封裝了相關邏輯。
logger.error("ex: ", e);
17:38:23.665 [main] ERROR com.liyao.s.Main - ex:
java.lang.RuntimeException: testEx
at com.liyao.s.Main.g(Main.java:113) ~[classes/:na]
at com.liyao.s.Main.main(Main.java:105) ~[classes/:na]
日誌框架中都提供了包含Throwable類型參數的日誌打印接口:
void xxx(String var1, Throwable var2);
內部會處理異常棧,我們不必再額外處理異常棧信息。