java 异常嵌套报错, 打印 源异常信息 异常链信息

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)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值