Getting different results for getStackTrace()[2].getMethodName()
出于日志记录的目的,我创建了一个方法logTitle(),它打印出TestNG测试的调用方法名称。 示例代码如下。
1 2 3 4 5 6 7 8 |
...
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.
因此,唯一有保证的方法是使用方面,或者使用其他自定义方式收集堆栈跟踪。但是你可以将这种方法与回退结合起来以某种方式获取当前方法的名称(例如,当你的
获得测试方法名称的简单方法是使用
只需将名称存储在某处并在日志中使用它(为什么不在
我的猜测,正如MeBigFatGuy所提到的那样。这可能是因为在进行内联优化方法时,IBM / Oracle JVM的JIT编译器的实现/默认值不同。
我建议在dev unix框中运行代码
1 | -Xjit:disableInlining |
并查看问题是否消失。
如果这对您有用,那么测试可能没问题,但正如Alexey Adamovskiy回答中所提到的,我们不能相信java包含在堆栈帧中。
也可以看看:
- 在优化过程中Java内联方法会不会?
- 有选择地禁用JIT编译器
-
Java方法内联
性能考虑因素
我认为它导致问题的具体深度在你的场景中是2。
所以,而不是写作
如果你写
1 2 3 4 5 6 7 8 9 10 |
它仅适用于JDK 1.5+
另一种选择如下:
或者较慢的选项是:
因为这将每次创建一个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。我做了
编辑: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的底部开始;这对于检测以递归方式调用记录器的异常情况(可能来自记录的对象的
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); |
然后在