Получение разных результатов для getStackTrace() [2].getMethodName()
В целях ведения журнала я создал метод 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 - ------------------------------------
Наш un unix box возвращает по-разному:
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 используется версия Java для Java, в то время как все остальные используют Oracle версию Java, но не уверены, является ли это виновником или нет.
Любые идеи?
Ответы
Ответ 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 каждый раз.
Надеюсь, что это поможет тебе.
Ответ 2
Простейший способ иметь имя метода тестирования - использовать @BeforeMethod
и вводить Method
. См. Документацию TestNG, здесь.
Просто сохраните имя где-нибудь и используйте его в своем журнале (почему бы и нет в @AfterMethod
?)
Ответ 3
От Javadoc:
Некоторые виртуальные машины могут при некоторых обстоятельствах опустить один или несколько кадров стека из трассировки стека. В крайнем случае виртуальная машина, которая не имеет информации о трассировке стека, относящейся к этому методу, разрешает возвращать массив нулевой длины из этого метода.
Таким образом, единственный гарантированный способ сделать это - использовать аспекты или собирать трассировку стека с помощью другого пользовательского способа. Но вы можете комбинировать этот подход с откатом, чтобы каким-то образом получить текущее имя метода (для случая, когда ваш метод logTitle
будет встроен). Например, здесь можно найти здесь. Опять же, нет гарантии, но больше шансов.
Ответ 4
Мое предположение, и как упоминалось MeBigFatGuy. Это может произойти из-за разницы в реализации/дефолтах JIT-компилятора IBM/Oracle JVM при выполнении оптимизации метода.
Я предлагаю запустить код в блоке dev unix с помощью
-Xjit:disableInlining
и посмотрите, исчезнет ли проблема.
Если это сработает для вас, это может быть хорошо для тестирования, но, как упоминалось в Алексее Адамовском, мы не можем доверять java, чтобы быть в кадрах стека.
См. также:
Ответ 5
Я предполагаю, что поведение специфично для 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()
, потому что однажды обнаружил пустую трассировку стека. Насколько я помню, JVM Oracle пропускает трассировки стека, когда одно и то же исключение перебрасывается снова и снова.
EDIT: Java 8-way
Optional<StackTraceElement> ste = Arrays.stream(new RuntimeException().getStackTrace())
.filter(x -> !x.getClassName().equals(Utils.class.getName()))
.findFirst();
Ответ 6
Log4J находит имя метода, ища вниз трассировку стека, пока не найдет имя целевого класса, которое должно быть передано, затем читает метод имя.
В вашем коде вы можете использовать подобный метод - вместо статического метода Utils вы можете создать экземпляр в своем тесте, пройдя в классе теста:
Utils utils = new Utils(MyTest.class);
Затем используйте предыдущую технику поиска в методе Utils.logTitle()
.
Utils.logTitle()
будет искать вперед через элементы трассировки стека только что созданного Throwable, пока не найдет первый элемент с желаемым целевым классом.
Ответ 7
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;
}