动态获取当前行号

38

在Java中是否有一种通过反射或某些强大的API动态获取当前行号的方法?就像当异常发生时,堆栈跟踪中打印出行号一样:

at weblogic.rmi.cluster.ClusterableRemoteRef.invoke(ClusterableRemoteRef.java:348)

现在有没有一种像下面代码那样打印或记录的方法?

log.error("Error in: " + this.getClass.getName() + "at line #"+ this.getClass.getActualLine());

你可能会问,为什么我不直接打印行号呢?因为在特定的log.error()方法调用之前,代码可能会被删除或添加。


2
日志API的PatternLayout难道不很方便吗? - sadhu
你可以创建一个 new Throwable 并访问堆栈跟踪元素。参见 https://dev59.com/zHVD5IYBdhLWcg3wBWzd - Andreas Fester
3
@Andreas:你不需要一个ThrowableThread.getStackTrace()就足够了。 (翻译:你可以使用Thread.getStackTrace()方法获取线程的堆栈轨迹信息,无需创建一个Throwable对象。) - Joachim Sauer
@Joachim Throwable方法在同一个地方起作用,只是Thread.currentThread().getStackTrace()不会... - Andreas Fester
1
啊,我明白了——数组的元素是不同的…… - Andreas Fester
显示剩余2条评论
5个回答

53

您可以创建一个Throwable并使用其StackTraceElements

  System.err.println(new Throwable().getStackTrace()[0].getLineNumber());

如@Joachim所说,您也可以使用Thread.getStackTrace(),例如:

  System.err.println(Thread.currentThread().getStackTrace()[1].getLineNumber());

请注意第二种方法返回的数组略有不同 - 您需要使用索引为1的数组元素来获取当前行号,因为它将getStackTrace()自身调用作为第一个元素。

还要注意@Joachim答案中有关记录和性能的评论。


1
在某些JVM上可能会产生不同的结果。至少对于AIX的IBM JVM而言,堆栈包括可抛出的构造函数。请参阅getStackTrace的javadoc:数组的第零个元素(假定数组的长度非零)表示堆栈顶部,即序列中最后一个方法调用。通常,这是创建和抛出此throwable的点。-通常情况下,并不保证如此。因此,Brian_Entei的解决方案更具有弹性。 - Vsevolod Golovanov
Thread.currentThread().getStackTrace()[2].getLineNumber() 帮助了我。 - Muhammad Shoaib Murtaza

16
首先:如果有任何需要,日志记录模式(或布局器或者你的日志框架所称呼的部分)应该处理这个。你代码中的日志调用只应该写入实际业务信息。关于位置信息应该由日志记录器添加。
其次:获取这种操作是昂贵的(以时间为衡量单位),因为Java并不为此进行优化。在运行时,JVM需要检查其状态,加载/解析调试信息,并找到与给定指令对应的行号。这就是为什么这种信息通常仅在异常发生时才被提供(在这种情况下,我们已经有了问题,并且知道花费的时间通常是值得的)。
最后但并非最不重要的:如果出于某种原因您需要自己使用该信息,则可以使用Thread.getStackTrace()并检查其中的第二个StackTraceElement

15

我使用Thread.currentThread().getStackTrace()方法创建了一组函数,它们共同工作以生成调用第一个方法的代码行号,就像这样:

/** @return The line number of the code that ran this method
 * @author Brian_Entei */
public static int getLineNumber() {
    return ___8drrd3148796d_Xaf();
}

/** This methods name is ridiculous on purpose to prevent any other method
 * names in the stack trace from potentially matching this one.
 * 
 * @return The line number of the code that called the method that called
 *         this method(Should only be called by getLineNumber()).
 * @author Brian_Entei */
private static int ___8drrd3148796d_Xaf() {
    boolean thisOne = false;
    int thisOneCountDown = 1;
    StackTraceElement[] elements = Thread.currentThread().getStackTrace();
    for(StackTraceElement element : elements) {
        String methodName = element.getMethodName();
        int lineNum = element.getLineNumber();
        if(thisOne && (thisOneCountDown == 0)) {
            return lineNum;
        } else if(thisOne) {
            thisOneCountDown--;
        }
        if(methodName.equals("___8drrd3148796d_Xaf")) {
            thisOne = true;
        }
    }
    return -1;
}

希望这可以帮到你!我将它们放在一个工具类中,以便它们不会妨碍你的其他操作,但又很容易访问。第二个方法是私有的,以防止除了第一个方法之外的任何其他方法调用它,以确保它始终正常工作。


看起来现在没问题了,哇。我很高兴它对你们有效。 - Brian_Entei
1
那很好用。此外,在堆栈跟踪中可能会有许多来自系统文件的调用。因此,我通过进行以下操作来查找我的类: if ((element.className.contains("我的包名")) && (!element.className.contains("包含我的日志函数的类")) ) - Liker777

7
从JDK9开始,您可以使用StackWalker。相比使用getStackTrace(),这使我获得了约50%的速度提升。
int get_line_number() {
    return StackWalker.getInstance(StackWalker.Option.SHOW_HIDDEN_FRAMES).walk(
      (s) -> s.skip(1).findFirst()).get().getLineNumber();
}

我的流处理可能还需改进,但我对此毫无经验,并且我总的来说并不喜欢流处理,所以请随意编辑我的帖子以改进这个部分。


FYI,这不是 Groovy 的正确语法。 - U.V.

3

可以使用以下代码片段获取异常行号。在您的util类中使用以下工具方法。

 public static int getExceptionLineNumber(Exception e, String methodNameExp) 
 {
    StackTraceElement ele = Arrays.stream(e.getStackTrace()).filter(o -> o.getMethodName().equals(methodNameExp)).findAny().orElse(null);
    if(ele != null){
        return ele.getLineNumber();
    }
    return -1;
 }

在 catch 块中,您可以像下面这样调用:

catch (Exception e){
        logger.info("Exception line number "+ Util.getExceptionLineNumber(e, new Object(){}.getClass().getEnclosingMethod().getName()));
    }

希望这个有效! :)

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