如何打印一个异常?
分两种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);
内部会处理异常栈,我们不必再额外处理异常栈信息。