获取getStackTrace()[2] .getMethodName()的不同结果

时间:2016-06-20 22:10:20

标签: java testng

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

@Test
public void test1() throws Exception {
    method1();
}

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

...

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()结果。

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

2016-06-20 14:22:06 INFO  - ------------------------------------
2016-06-20 14:22:06 INFO  - method1
2016-06-20 14:22:06 INFO  - ------------------------------------

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

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版本,但不确定这是否是罪魁祸首。

有什么想法吗?

7 个答案:

答案 0 :(得分:4)

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

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

答案 1 :(得分:4)

来自Javadoc

  

在某些情况下,某些虚拟机可能会从堆栈跟踪中省略一个或多个堆栈帧。在极端情况下,允许没有关于此throwable的堆栈跟踪信息的虚拟机从此方法返回零长度数组。

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

答案 2 :(得分:2)

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

我建议使用

在dev unix框中运行代码
-Xjit:disableInlining  

并查看问题是否消失。

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

另见:

答案 3 :(得分:1)

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

// 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方式

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

答案 4 :(得分:1)

我认为它导致问题的具体深度在您的方案中为2。

所以,而不是写

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

如果你写

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 +

另一个选项如下:

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

或者较慢的选项是:

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

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

希望能帮到你。

答案 5 :(得分:0)

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

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

Utils utils = new Utils(MyTest.class);

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

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

答案 6 :(得分:0)

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

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

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