【问题标题】:Fetch only first N lines of a Stack Trace仅获取堆栈跟踪的前 N ​​行
【发布时间】:2014-03-09 12:38:10
【问题描述】:

我有一个从 ID 调用返回对象的工厂方法。

模拟代码:

public static Object getById(String id) {
    Object o = CRUD.doRecovery(Class, id);
    if(o == null) {
         printLogMessage("recovery by ID returned Null: " + id);
         // would really like to show only a few lines of stack trace.
    }
    return o;
}

如何只显示堆栈跟踪的前 N ​​行(这样我就知道该方法的调用者),而无需将整个堆栈跟踪转储到日志上或不必依赖外部库?

【问题讨论】:

  • 请不要使用堆栈跟踪来了解方法的调用者。它非常昂贵(比较this question,它也有一些分析数据。)。而是使用第二个参数,并通过合同要求您的调用者使用一些有意义的自我识别 ID。

标签: java exception stack-trace


【解决方案1】:

以下链接中的片段适用于 N 行。

低于 sn-p 有助于剥离异常堆栈跟踪。

public class LoggerHelper {

private static final String SEPARATOR = "\r\n";
private static final String CAUSE_CAPTION = "Caused by: ";
private static final String SUPPRESSED_CAPTION = "Suppressed: ";

/**
 * The first 10 lines of exception stack information are returned by default
 *
 * @param e
 * @return
 */
public static String printTop10StackTrace(Throwable e) {
    if (e == null) {
        return "";
    }
    return printStackTrace(e, 20);
}

public static String printStackTrace(Throwable e, int maxLineCount) {
    if (e == null || maxLineCount <= 0) {
        return "";
    }
    StringBuilder sb = new StringBuilder(maxLineCount * 10);
    sb.append(e.toString()).append(SEPARATOR);
    StackTraceElement[] trace = e.getStackTrace();
    if (trace == null) {
        return e.toString();
    }
    int count = maxLineCount > trace.length ? trace.length : maxLineCount;
    int framesInCommon = trace.length - count;
    for (int i = 0; i < count; i++) {
        sb.append("\tat ").append(trace[i]).append(SEPARATOR);
    }
    if (framesInCommon != 0) {
        sb.append("\t... ").append(framesInCommon).append(" more").append(SEPARATOR);
    }
    // Print suppressed exceptions, if any
    Throwable[] suppressedExceptions = e.getSuppressed();
    if (ArrayUtils.isNotEmpty(suppressedExceptions)) {
        for (Throwable suppressedException : suppressedExceptions) {
            sb.append(printEnclosedStackTrace(suppressedException, maxLineCount, trace, SUPPRESSED_CAPTION, "\t"));
        }
    }
    // Print cause, if any
    Throwable cause = e.getCause();
    if (cause != null) {
        sb.append(printEnclosedStackTrace(cause, maxLineCount, trace, CAUSE_CAPTION, ""));
    }
    return sb.toString();
}

private static String printEnclosedStackTrace(Throwable e, int maxLineCount, StackTraceElement[] enclosingTrace,
                                              String caption, String prefix) {
    StringBuilder sb = new StringBuilder(maxLineCount * 5);
    StackTraceElement[] trace = e.getStackTrace();
    int m = trace.length - 1;
    int n = enclosingTrace.length - 1;
    while (m >= 0 && n >= 0 && trace[m].equals(enclosingTrace[n])) {
        m--;
        n--;
    }
    int count = maxLineCount > (m + 1) ? (m + 1) : maxLineCount;
    int framesInCommon = trace.length - count;
    // Print our stack trace
    sb.append(prefix).append(caption).append(e.toString()).append(SEPARATOR);
    for (int i = 0; i < count; i++) {
        sb.append(prefix).append("\tat ").append(trace[i]).append(SEPARATOR);
    }
    if (framesInCommon != 0) {
        sb.append(prefix).append("\t... ").append(framesInCommon).append(" more").append(SEPARATOR);
    }
    // Print suppressed exceptions, if any
    Throwable[] suppressedExceptions = e.getSuppressed();
    if (ArrayUtils.isNotEmpty(suppressedExceptions)) {
        for (Throwable suppressedException : suppressedExceptions) {
            sb.append(printEnclosedStackTrace(suppressedException, maxLineCount, trace, SUPPRESSED_CAPTION, prefix + "\t"));
        }
    }
    // Print cause, if any
    Throwable cause = e.getCause();
    if (cause != null) {
        sb.append(printEnclosedStackTrace(cause, maxLineCount, trace, CAUSE_CAPTION, prefix));
    }
    return sb.toString();
 }

}

【讨论】:

【解决方案2】:

显示第五行的示例:

        final int nbLinesToShow = 5;
        try {
            /* your code here */
        } catch (final NullPointerException e) {
            // catch error
            final StackTraceElement[] elements = e.getStackTrace();
            System.err.println(
                    "===================================== \n" + "[ERROR] lorem ipsum");
            for (int i = 0; i < nbLinesToShow; i++) {
                System.err.println(elements[i]);
            }
        }

【讨论】:

  • 提出的问题没有抛出异常;因此,这不是给定问题的答案。
【解决方案3】:

对于 e.printStackTrace() 的缩写版本:

        Exception e = ...
        System.out.println(e.toString());
        StackTraceElement[] elements = e.getStackTrace();
        for(int i = 0; i<elements.length && i < STACK_TRACE_LIMIT; i++) {
            System.out.println("\tat "+elements[i]);
        }

STACK_TRACE_LIMIT 替换为您想要的限制或删除 &amp;&amp; i &lt; STACK_TRACE_LIMIT 以重现简单堆栈跟踪的输出(例如,没有嵌套异常)

最内层的方法调用位于索引 0,main 位于索引长度 1。

【讨论】:

    【解决方案4】:

    番石榴可以提供帮助。例如,我们只想查看前十行:

    log.error("Error:", Joiner.on("\n").join(Iterables.limit(asList(ex.getStackTrace()), 10)));
    

    【讨论】:

    • 它应该是Joiner.on("\n").join(Iterables.limit(Arrays.asList(ex.getStackTrace()), 10))
    【解决方案5】:

    此方法显示堆栈跟踪的i 行,跳过前两行。

    public static String traceCaller(Exception ex, int i) {
        StringWriter sw = new StringWriter();
        PrintWriter pw = new PrintWriter(sw);
        StringBuilder sb = new StringBuilder();
        ex.printStackTrace(pw);
        String ss = sw.toString();
        String[] splitted = ss.split("\n");
        sb.append("\n");
        if(splitted.length > 2 + i) {
            for(int x = 2; x < i+2; x++) {
                sb.append(splitted[x].trim());
                sb.append("\n");
            }
            return sb.toString();
        }
        return "Trace too Short.";
    }
    

    前两行是异常名称和调用traceCaller() 的方法。如果要显示这些行,请对其进行调整。

    感谢 @BrianAgnew (stackoverflow.com/a/1149712/1532705) 提供 StringWriter PrintWriter 的想法

    【讨论】:

    【解决方案6】:

    如果您只想截断堆栈跟踪,您可以将整个堆栈跟踪打印到 StringWriter,然后删除您不想要的:

    public static void main(String[] args) throws ParseException {
        try {
            throw new Exception("Argh!");
        } catch (Exception e) {
            System.err.println(shortenedStackTrace(e, 1));
        }
    }
    
    public static String shortenedStackTrace(Exception e, int maxLines) {
        StringWriter writer = new StringWriter();
        e.printStackTrace(new PrintWriter(writer));
        String[] lines = writer.toString().split("\n");
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < Math.min(lines.length, maxLines); i++) {
            sb.append(lines[i]).append("\n");
        }
        return sb.toString();
    }
    

    或者,使用e.getStackTrace() 获取StackTraceElement[] 数组。这为您提供调用者堆栈(从内部到外部),但不提供错误消息。您必须使用 e.getMessage() 来获取错误消息。

    一些日志框架可以配置为自动截断堆栈跟踪。例如。有关 log4j 配置,请参阅this question and answer

    如果您只想查看代码中任意位置的堆栈跟踪,可以从Thread.currentThread() 对象中获取元素:

    Thread.currentThread().getStackTrace();
    

    【讨论】:

    • "无需依赖外部库"
    【解决方案7】:

    根据您的要求,我假设您没有要处理的异常。在这种情况下,您可以从以下位置获取当前堆栈跟踪:

    StackTraceElement[] elements = Thread.currentThread().getStackTrace()
    

    这将告诉你几乎所有你需要知道的关于你在代码中来自哪里的信息。

    【讨论】:

    • 问题中要求的非异常用例的最全面答案。只需以您方便的格式输出“元素”数组的前几个元素。提醒一句,Stacktrace 的计算成本异常。您正在查看 4 个数量级的性能恶化。不要将其用于高性能功能!进行分析,然后将这个想法放入 bin 并传递一些标识符作为参数。然后,每个调用者都必须使用某种自识别参数来通过合约调用该方法。不那么方便,但要快得多。
    • 性能不是问题,因为我们正在处理逻辑异常,例如当应用程序的某些部分引用不存在的实体 ID 时。然后我们可以确定罪犯。我认为让调用者标识自己是一个额外的耦合层,我宁愿用性能来付出代价。
    • 但是mikea的回答很优雅,我在绿化它。
    • 最内层的方法调用位于索引 0,main 位于索引长度 1。请参阅my answer 以获取重现堆栈跟踪的示例(如果您碰巧拥有完整的异常对象)。
    【解决方案8】:

    您可以使用ex.getStackTrace() 获取堆栈元素,StackTraceElement 包含一行完整堆栈,然后 print print 你想要什么。

    StackTraceElement[] elements = ex.getStackTrace();
    print(elements[0]);
    

    【讨论】:

      猜你喜欢
      • 2010-10-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-12
      • 2010-11-07
      • 1970-01-01
      相关资源
      最近更新 更多