获取堆栈跟踪中的前N行

13

我有一个工厂方法,根据给定的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行(以便了解方法的调用者),而不将整个堆栈跟踪转储到日志中或依赖于外部库?


请勿使用堆栈跟踪来了解方法的调用者。这是非常昂贵的(请参见此问题,其中还有一些分析数据)。相反,请使用第二个参数,并通过合同要求您的调用者使用一些有意义的自我识别ID。 - hiergiltdiestfu
8个回答

16
你可以使用 ex.getStackTrace() 来获取堆栈元素,StackTraceElement 包含完整堆栈的一行,然后打印任何你想要的内容。
StackTraceElement[] elements = ex.getStackTrace();
print(elements[0]);

11

我假设你的问题中并没有涉及到需要处理异常的情况。在这种情况下,你可以通过以下方式获取当前堆栈跟踪:

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

这将基本上告诉您有关代码中您来自何处的所有信息。


1
问题中要求非异常用例的最全面答案。以您方便的格式输出“elements”数组的前几个元素即可。但是请注意,堆栈跟踪计算成本极高。您将面临4个数量级的性能恶化。不要在高性能函数中使用此功能!进行分析后,将该想法放入垃圾桶并传递一些标识符作为参数。然后,每个调用者都必须按合同使用某种自我标识参数来调用该方法。虽然不太方便,但速度要快得多。 - hiergiltdiestfu
性能不是问题,因为我们正在处理逻辑异常,例如当应用程序的某个部分引用不存在的实体ID时。然后我们可以锁定罪犯。我认为让调用者自己识别自己是一种额外的耦合层,我宁愿在性能上付出代价。 - Mindwin Remember Monica
但是mikea的答案非常优雅,我正在赞同它。 - Mindwin Remember Monica
最内层的方法调用在索引0处,main方法在索引长度-1处。如果您恰好拥有完整的异常对象,请参见我的答案以重现堆栈跟踪的示例。 - Josiah Yoder

4
如果你只想截取堆栈跟踪信息,你可以将整个堆栈跟踪信息打印到 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配置的此问题和答案

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

Thread.currentThread().getStackTrace();

1
不必依赖外部库 - Mindwin Remember Monica

3
该方法显示堆栈跟踪的 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 的思路。


感谢 @BrianAgnew(https://dev59.com/TnM_5IYBdhLWcg3w9oWG#1149712)提供的 StringWriter PrintWriter 想法。 - Mindwin Remember Monica
我认为你应该将这个评论移动到你的答案中。 - Luiggi Mendoza

1

Guava可以帮助。例如,我们想要查看前十行:

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

它应该是Joiner.on("\n").join(Iterables.limit(Arrays.asList(ex.getStackTrace()), 10)) - Kushal Bhalaik

1

对于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替换为您想要的限制或删除&& i < STACK_TRACE_LIMIT以重现简单堆栈跟踪的输出(例如,没有嵌套异常)。
最内层的方法调用位于索引0,主函数在索引length-1。

0

显示前五行的示例:

        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]);
            }
        }

在所提出的问题中没有抛出任何异常,因此这不是对给定问题的答案。 - TamaMcGlinn

-1

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

以下代码片段可帮助剥离异常堆栈跟踪。

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();
 }

}


欢迎提供解决方案的链接,但请确保您的答案即使没有链接也是有用的:在链接周围添加上下文,以便其他用户了解它的内容和原因,然后引用您链接的页面中最相关的部分,以防目标页面无法访问。仅仅提供链接的答案可能会被删除。 - 4b0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接