仅获取堆栈跟踪的前 N ​​行

Posted

技术标签:

【中文标题】仅获取堆栈跟踪的前 N ​​行【英文标题】:Fetch only first N lines of a Stack Trace 【发布时间】: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。 【参考方案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();
 

【讨论】:

欢迎提供指向解决方案的链接,但请确保您的答案在没有它的情况下有用:add context around the link 这样您的其他用户就会知道它是什么以及为什么存在,然后引用最多您链接到的页面的相关部分,以防目标页面不可用。 Answers that are little more than a link may be deleted.【参考方案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 (***.com/a/1149712/1532705) 提供 StringWriter PrintWriter 的想法

【讨论】:

感谢@BrianAgnew (***.com/a/1149712/1532705) 提出StringWriter PrintWriter 的想法。 IMO,您应该将此评论作为您答案的一部分。【参考方案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]);

【讨论】:

以上是关于仅获取堆栈跟踪的前 N ​​行的主要内容,如果未能解决你的问题,请参考以下文章

如何从像 Asp.Net 这样的堆栈跟踪中获取“源错误”?

如何获取 gdb 调用堆栈跟踪?

在应用程序崩溃/C++ 期间获取堆栈跟踪

在进行 HTTP 调用时显示以下堆栈跟踪 [重复]

为啥我的异常堆栈跟踪总是指向最后一个方法行?

从堆栈跟踪中获取更多详细信息