关于java:获取getStackTrace()[2] .getMethodName()的不同结果

Getting different results for getStackTrace()[2].getMethodName()

出于日志记录的目的,我创建了一个方法logTitle(),它打印出TestNG测试的调用方法名称。 示例代码如下。

1
2
3
4
5
6
7
8
@Test
public void test1() throws Exception {
    method1();
}

public static void method1() throws Exception {
    Utils.logTitle(2);
}

...

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public static void logTitle(Integer level) throws Exception {

    // Gets calling method name
    String method = Thread.currentThread().getStackTrace()[2].getMethodName();
    // This would get current method name
    switch (level) {
    case 1:
        logger.info("=======================================================");
        logger.info(method);
        logger.info("=======================================================");
        break;
    case 2:
        logger.info("------------------------------------");
        logger.info(method);
        logger.info("------------------------------------");
        break;
    case 3:
        logger.info("---------------------");
        logger.info(method);
        logger.info("---------------------");
        break;
    case 4:
        logger.info("---------" + method +" ------------");
        break;
    default:
        logger.info(method);
    }
}

问题是我在两台不同的机器上获得了不同的logTitle()结果。

每个人的笔记本电脑都正确返回:

1
2
3
2016-06-20 14:22:06 INFO  - ------------------------------------
2016-06-20 14:22:06 INFO  - method1
2016-06-20 14:22:06 INFO  - ------------------------------------

我们的dev unix框以不同的方式返回:

1
2
3
2016-06-20 14:42:26 INFO  - ------------------------------------
2016-06-20 14:42:26 INFO  - logTitle
2016-06-20 14:42:26 INFO  - ------------------------------------

这适用于其他人的笔记本电脑,而不是dev unix盒子。 我认为dev unix框使用的是IBM的Java版本,而其他人都在使用Oracle的Java版本,但不确定这是否是罪魁祸首。

有任何想法吗?


来自Javadoc:

Some virtual machines may, under some circumstances, omit one or more stack frames from the stack trace. In the extreme case, a virtual machine that has no stack trace information concerning this throwable is permitted to return a zero-length array from this method.

因此,唯一有保证的方法是使用方面,或者使用其他自定义方式收集堆栈跟踪。但是你可以将这种方法与回退结合起来以某种方式获取当前方法的名称(例如,当你的logTitle方法将被内联时)。例如,它可以在这里找到。再次,不能保证,但更好的机会。


获得测试方法名称的简单方法是使用@BeforeMethod并注入Method。请参阅此处的TestNG文档。

只需将名称存储在某处并在日志中使用它(为什么不在@AfterMethod中?)


我的猜测,正如MeBigFatGuy所提到的那样。这可能是因为在进行内联优化方法时,IBM / Oracle JVM的JIT编译器的实现/默认值不同。

我建议在dev unix框中运行代码

1
-Xjit:disableInlining

并查看问题是否消失。

如果这对您有用,那么测试可能没问题,但正如Alexey Adamovskiy回答中所提到的,我们不能相信java包含在堆栈帧中。

也可以看看:

  • 在优化过程中Java内联方法会不会?
  • 有选择地禁用JIT编译器
  • Java方法内联
    性能考虑因素

我认为它导致问题的具体深度在你的场景中是2。

所以,而不是写作

1
String method = Thread.currentThread().getStackTrace()[2].getMethodName();

如果你写

1
2
3
4
5
6
7
8
9
10
StackTraceElement[] ste = Thread.currentThread().getStackTrace();
String method = null;
boolean doNext = false;
for (StackTraceElement s : ste) {
       if (doNext) {
          method = s.getMethodName();
          return;
       }
       doNext = s.getMethodName().equals("getStackTrace");
   }

它仅适用于JDK 1.5+

另一种选择如下:

1
String method = new Object(){}.getClass().getEnclosingMethod().getName();

或者较慢的选项是:

1
String method = new Exception().getStackTrace()[0].getMethodName();

因为这将每次创建一个Exception实例。

希望能帮到你。


我猜这种行为是特定于JVM的。在过去,我想出了这个解决方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
// find first stack trace entry that is not in this class
Optional<StackTraceElement> ste = Iterables.tryFind(
        Arrays.asList(new RuntimeException().getStackTrace()),
        new Predicate<StackTraceElement>() {
            @Override
            public boolean apply(StackTraceElement input) {
                return !input.getClassName().equals(PutYourClassHere.class.getName());
            }
        });

if (ste.isPresent()) {
    LOG.trace("Method called by: {}.{}", ste.get().getClassName(), ste.get().getMethodName());
}

该片段使用的是Google Guava,因为它适用于Java 7.如果您使用的是Java 8,则可以使用Streams API和lambdas。我做了ste.isPresent()检查,因为我遇到了一次空堆栈跟踪。据我记得,当反复??抛出相同的异常时,Oracle JVM正在跳过堆栈跟踪。

编辑:Java 8方式

1
2
3
Optional<StackTraceElement> ste = Arrays.stream(new RuntimeException().getStackTrace())
            .filter(x -> !x.getClassName().equals(Utils.class.getName()))
            .findFirst();

Log4j 2使用Logger的完全限定类名来定位调用Logger的类和方法。找到位置的代码如下。随意使用它。

请注意,循环从stacktrace的底部开始;这对于检测以递归方式调用记录器的异常情况(可能来自记录的对象的toString()方法)是必要的。在这种情况下,我们想要报告调用Logger的第一个类/方法,而不是最后一个,所以我们别无选择,只能从下往上走堆栈跟踪。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public static StackTraceElement calcLocation(final String fqcnOfLogger) {
    if (fqcnOfLogger == null) {
        return null;
    }
    // LOG4J2-1029 new Throwable().getStackTrace is faster
    // than Thread.currentThread().getStackTrace().
    final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
    StackTraceElement last = null;
    for (int i = stackTrace.length - 1; i > 0; i--) {
        final String className = stackTrace[i].getClassName();
        if (fqcnOfLogger.equals(className)) {
            return last;
        }
        last = stackTrace[i];
    }
    return null;
}


Log4J通过向下搜索堆栈跟踪找到方法名称,直到找到必须传入的目标类名,然后读取方法名称。

在您的代码中,您可以使用类似的技术 - 而不是静态方法Utils您可以在测试中创建一个实例,传入测试的类:

1
Utils utils = new Utils(MyTest.class);

然后在Utils.logTitle()方法中使用前面提到的搜索技术。

Utils.logTitle()将向前搜索新创建的Throwable的堆栈跟踪元素,直到找到具有所需目标类的第一个元素。