我们经常通过 try catch 代码块包住一段可能出现异常的代码,同时在 catch 块中打印异常信息,如下所示:
public static void main(String args[]) {
try {
a();
} catch (HighLevelException e) {
e.printStackTrace();
}
}
当然通常情况我们会使用日志框架,不会直接使用 e.printStackTrace() 方法打印异常栈,但是日志框架同样会输出整个异常栈的信息,如下所示:
logger.error("error message", e);
大部分情况下,我们是不需要查看整个异常栈的信息就可以定位问题的,而且大量的异常栈信息堆积到日志文件中,对硬盘的消耗较大。如果同步到 ELK 中,对日志的内容进行分析,对 ES 存储的消耗也会很大。
那怎么限制异常栈的行数呢?
首先,我们来分析 e.printStackTrace() 方法的源码,看看它是如何打印异常栈信息的。
printStackTrace() 方法在 java.lang.Throwable 类中,方法的调用链路如下所示:
java.lang.Throwable#printStackTrace() -> java.lang.Throwable#printStackTrace(java.io.PrintStream) -> java.lang.Throwable#printStackTrace(java.lang.Throwable.PrintStreamOrWriter)
我们来看真正执行打印的方法:
private void printStackTrace(PrintStreamOrWriter s) {
// Guard against malicious overrides of Throwable.equals by
// using a Set with identity equality semantics.
Set<Throwable> dejaVu =
Collections.newSetFromMap(new IdentityHashMap<Throwable, Boolean>());
dejaVu.add(this);
synchronized (s.lock()) {
// Print our stack trace
s.println(this);
StackTraceElement[] trace = getOurStackTrace();
for (StackTraceElement traceElement : trace)
s.println("\tat " + traceElement);
// Print suppressed exceptions, if any
for (Throwable se : getSuppressed())
se.printEnclosedStackTrace(s, trace, SUPPRESSED_CAPTION, "\t", dejaVu);
// Print cause, if any
Throwable ourCause = getCause();
if (ourCause != null)
ourCause.printEnclosedStackTrace(s, trace, CAUSE_CAPTION, "", dejaVu);
}
}
public StackTraceElement[] getStackTrace() {
return getOurStackTrace().clone();
}
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;
}
通过上面的代码,可以知道 e.printStackTrace() 方法中打印的内容是 StackTraceElement[],而且在接下来会输出 suppressed exceptions 和 cause 信息。
getStackTrace() 已经提供了 public 方法,可以通过 e.getStackTrace() 方式直接获取,不需要通过反射调用下面的私有方法。
新建自定义日志帮助类,限制异常栈的打印行数。
在自定义类之前,先准备两个会输出异常信息的测试类,一个(ExceptionTest1)会输出 Caused by,一个(ExceptionTest2)会输出 Suppressed。
public class ExceptionTest1 {
public static void main(String args[]) {
try {
a();
} catch (HighLevelException e) {
System.out.println(LoggerHelper.printTop10StackTrace(e));
}
}
static void a() throws HighLevelException {
try {
b();
} catch (MidLevelException e) {
throw new HighLevelException(e);
}
}
static void b() throws MidLeve