Подтвердить что ты не робот

Получение разных результатов для 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, но не уверены, является ли это виновником или нет.

Любые идеи?

4b9b3361

Ответ 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;
}