1.可以只打印异常源头;
2.仅打印特定包下的调用栈, 方便排查问题;
(场景 如:全局异常捕获时快速定位问题; 执行定时任务时, 报错代码调用链信息 精简后保存到数据库, 省得来回翻日志文件)
2021-05-24 发现调用链路没打印, 调整
2021-06-xx 可以根据包名前缀, 精简打印链路
/**
* 打印异常链路, 因为遇到空指针时 e.getMessage()返回的是null, 所以打印异常抛出链路能够快速定位报错代码
*
* @Author: liu
* @Date: 2021/07/17 22:31
* @Version 1.4
*/
public class ExceptionStackTraceUtil{
private static final Logger logger = LoggerFactory.getLogger(ExceptionStackTraceUtil.class);
public static void main(String[] args) {
try {
call();
} catch (Exception e) {
// e.printStackTrace();
// String stb = printOrigin(e); // 只打印异常源头
// String stb = printLink(e);// 只打印异常链路(没有调用链)
// String packagePrefix = "com.xxx";// 设置包名前缀后, 可以把当前项目中的整个调用链 打印出来,
// String stb = printLink(e, packagePrefix);// 打印异常链路+调用链路(根据包名前缀);
String stb = printLink(e, new TreeSet<>(Arrays.asList("com.xxx","org")));// 多个前缀打印
// String stb = printLink(e, packagePrefix, false);// 跟e.printStackTrace(); 打印顺序一致
System.out.println(stb);
}
}
private static void call() throws Exception {
test();
}
/**
* 多层try catch, 测试跨方法调用时调用链路是否打印
*/
private static void test() throws Exception {
try {
try {
System.out.println("测试异常打印");
Integer i = 0;
i = 1 / i;
} catch (Exception e) {
throw new RuntimeException("出现除0异常", e);
}
} catch (Exception e) {
throw new Exception("通用异常处理", e);
}
}
// ======================================================================================
/**
* <pre>打印异常信息的源头, 有可能异常源头在源码中, 看情况使用</pre>
*/
static String printOrigin(Throwable e) {
try {
return buildExceptionLocation(getOriginException(e));
} catch (Exception ex) { // 防止工具类写的有bug, 直接返回原始信息
logger.error("打印异常源头失败", e);
}
return e.getMessage();
}
/**
* <pre>仅打印异常链, 模拟 e.printStackTrace(); 精简了多余的打印信息, 处理有多层try catch的场景</pre>
*/
public static String printLink(Throwable e) {
return printLink(e, null, true);
}
/**
* <pre>打印异常链+调用链, 模拟 e.printStackTrace(); 精简了多余的打印信息, 推荐使用</pre>
*/
public static String printLink(Throwable e, String packagePrefix) {
TreeSet<String> set = new TreeSet<>();
set.add(packagePrefix);
return printLink(e, set, true);
}
public static String printLink(Throwable e, Set<String> packagePrefixSet) {
return printLink(e, packagePrefixSet, true);
}
/**
* @param isDesc 是否倒叙, true-异常源头最先打印, false-外层异常最先打印
*/
public static String printLink(Throwable e, Set<String> packagePrefixSet, boolean isDesc) {
try {
List<Throwable> list = new ArrayList<>();
getExceptionLink(e, list);
StringBuilder stb = getExceptionStackTraceStr(list, packagePrefixSet, isDesc);
return stb.toString();
} catch (Exception ex) { // 防止工具类写的有bug, 直接返回原始信息
logger.error("打印异常链路失败", e);
}
return e.getMessage();
}
/**
* 获取异常源头
*/
private static Throwable getOriginException(Throwable e) {
Throwable cause = e.getCause();
if (cause == null) {
return e;
}
if (cause.equals(e)) {
return e;
} else {
return getOriginException(cause);
}
}
/**
* 获取异常链
*/
private static Throwable getExceptionLink(Throwable e, List<Throwable> list) {
Throwable cause = e.getCause();
if (cause == null) {
list.add(e);
return e;
}
if (cause.equals(e)) {
return e;
} else {
list.add(e);
return getExceptionLink(cause, list);
}
}
/**
* 构建异常信息, 如: Caused by: java.lang.Exception: xxx处理出错
*/
private static String buildExceptionLocation(Throwable e) {
StringBuilder stb = new StringBuilder();
stb.append("Caused by: ");
stb.append(e.getClass().getName());// 异常类型
stb.append(": ");
stb.append(e.getMessage());// e.getMessage()
stb.append("\n");
return stb.toString();
}
private static StringBuilder getExceptionStackTraceStr(List<Throwable> list, Set<String> packagePrefixSet, boolean isDesc) {
ArrayList<String> resultList = new ArrayList<>();
HashSet<StackTraceElement> stackTraceElementSet = new HashSet<>();
for (Throwable throwable : list) {
StringBuilder stb = new StringBuilder();
String str = buildExceptionLocation(throwable);
stb.append(str);
ArrayList<StackTraceElement> tempStackTraceElementList = new ArrayList<>();
if (packagePrefixSet == null || packagePrefixSet.isEmpty()) {
stb.append(buildCallLink(throwable)); // 报错的类(方法:错误行)
} else {
// 排除已经处理过的调用链
for (StackTraceElement stackTraceElement : throwable.getStackTrace()) {
if (!stackTraceElementSet.contains(stackTraceElement)) {
// 是指定包名前缀的才会保存, 过滤掉不想看的
if (isMatchPrefix(stackTraceElement.getClassName(), (packagePrefixSet))) {
tempStackTraceElementList.add(stackTraceElement);
}
}
}
stb.append(buildCallLink(tempStackTraceElementList.iterator())); // 报错的类(方法:错误行)
stackTraceElementSet.addAll(tempStackTraceElementList);// 存储已经处理过的调用链
}
resultList.add(stb.toString());
}
if (isDesc) {
Collections.reverse(resultList);// 反转打印顺序
}
StringBuilder stb = new StringBuilder();
for (String str : resultList) {
stb.append(str);
}
return stb;
}
/**
* 是否有匹配的前缀
*/
static boolean isMatchPrefix(String className, Set<String> packagePrefixSet) {
for (String prefix : packagePrefixSet) {
if (className == null || className.trim().length() <= 0) {
continue;
}
if (className.startsWith(prefix)) {
return true;
}
}
return false;
}
/**
* 构建异常类 如: at com.aaa.xxx.main(xxx.java:27)
*/
private static String buildCallLink(Throwable e) {
// 取堆栈信息对象 仅打印 异常链路
StackTraceElement stackTraceElement = e.getStackTrace()[0];
String className = stackTraceElement.getClassName(); // 包名+类名
String methodName = stackTraceElement.getMethodName(); // 方法名
String fileName = stackTraceElement.getFileName(); // 文件名+后缀
int lineNumber = stackTraceElement.getLineNumber(); // 行号
StringBuilder stb = new StringBuilder();
stb.append("\tat ").append(className).append(".").append(methodName).//
append("(").append(fileName).append(":").append(lineNumber).append(")").append("\n");
return stb.toString();
}
/**
* 打印调用链
* 比如: a->b->c
* at xx.a()
* at xx.b()
* at xx.c()
*/
private static String buildCallLink(Iterator<StackTraceElement> e) {
// 异常链路+调用链路(需要指定包名前缀)
StringBuilder stb = new StringBuilder();
while (e.hasNext()) {
StackTraceElement temp = e.next();
String className = temp.getClassName(); // 包名+类名
String methodName = temp.getMethodName(); // 方法名
String fileName = temp.getFileName(); // 文件名+后缀
int lineNumber = temp.getLineNumber(); // 行号
stb.append("\tat ").append(className).append(".").append(methodName).//
append("(").append(fileName).append(":").append(lineNumber).append(")").append("\n");
}
return stb.toString();
}
}
输出的结果
测试异常打印
Caused by: java.lang.Exception: 通用异常处理
at com.xxx.health.common.util.ExceptionStackTraceUtil.test(ExceptionStackTraceUtil.java:55)
at com.xxx.health.common.util.ExceptionStackTraceUtil.call(ExceptionStackTraceUtil.java:39)
at com.xxx.health.common.util.ExceptionStackTraceUtil.main(ExceptionStackTraceUtil.java:23)
Caused by: java.lang.RuntimeException: 出现除0异常
at com.xxx.health.common.util.ExceptionStackTraceUtil.test(ExceptionStackTraceUtil.java:52)
Caused by: java.lang.ArithmeticException: / by zero
at com.xxx.health.common.util.ExceptionStackTraceUtil.test(ExceptionStackTraceUtil.java:50)
==============================================================
2020/11/30 11:31
@Test
public void show() {
try {
try {
try {
System.out.println("测试异常打印");
int i = 0;
i = 1 / i;
} catch (Exception e) {
throw new RuntimeException("出现除0异常", e);
}
} catch (Exception e) {
throw new Exception("通用异常处理", e);
}
} catch (Exception e) {
// String stb = printSimpleLog(e); // 打印异常源头
String stb = printSimpleLogLink(e);// 打印异常链路
System.out.println(stb);
}
}
/**
* <pre>打印异常信息, 模拟 e.printStackTrace();</pre>
*/
private String printSimpleLogLink(Throwable e) {
List<Throwable> list = new ArrayList<>();
getOriginExceptionLink(e, list);
StringBuilder stb = new StringBuilder();
for (Throwable throwable : list) {
String str = printSimpleLog(throwable);
stb.append(str);
}
return stb.toString();
}
/**
* <pre>打印异常信息的链路</pre>
*/
private String printSimpleLog(Throwable e) {
String stackTraceStr = getStackTrace(e);
StringBuilder stb = new StringBuilder();
stb.append("Caused by: ");
// stb.append(e);// 2021-02-20 因为有遇到别人封装的异常重写了toStirng方法导致
// detailMessage丢失, 所以改用如下方式
stb.append(e.getClass().getName());// 异常类型
stb.append(": ");
stb.append(e.getMessage());// e.getMessage()
stb.append("\n\tat ");
stb.append(stackTraceStr);
stb.append("\n");
return stb.toString();
}
/**
* 获取异常链
*/
Throwable getOriginExceptionLink(Throwable e, List<Throwable> list) {
Throwable cause = e.getCause();
if (cause == null) {
list.add(e);
return e;
}
if (cause.equals(e)) {
return e;
} else {
list.add(e);
return getOriginExceptionLink(cause, list);
}
}
/**
* 获取异常源头
*/
Throwable getOriginException(Throwable e) {
Throwable cause = e.getCause();
if (cause == null) {
return e;
}
if (cause.equals(e)) {
return e;
} else {
return getOriginException(cause);
}
}
/**
* 取堆栈信息
*/
public String getStackTrace(Throwable e) {
// 取堆栈信息对象
StackTraceElement stackTraceElement = e.getStackTrace()[0];
String className = stackTraceElement.getClassName();// 报错的报名+类名
// Class<? extends StackTraceElement> aClass = stackTraceElement.getClass(); //堆栈跟踪对象 class java.lang.StackTraceElement
String fileName = stackTraceElement.getFileName(); // 报错的文件名+后缀
int lineNumber = stackTraceElement.getLineNumber(); // 报错的行号
String methodName = stackTraceElement.getMethodName(); // 报错的 方法名
// System.out.println(className);
// System.out.println(fileName);
// System.out.println(lineNumber);
// System.out.println(methodName);
StringBuilder stb = new StringBuilder();
stb.append(className).append(".").append(methodName).//
append("(").append(fileName).append(":").append(lineNumber).append(")");
return stb.toString();
}
输出 异常源头
Caused by: java.lang.ArithmeticException: / by zero
> at cn.Test.show(Test.java:78)
输出 异常链
Caused by: java.lang.Exception: 通用异常处理
> at cn.Test.show(Test.java:85)
Caused by: java.lang.RuntimeException: 出现除0异常
> at cn.Test.show(Test.java:82)
Caused by: java.lang.ArithmeticException: / by zero
> at cn.Test.show(Test.java:79)