From 76f11970f5557893d2e51a13df990eb4f1f7739a Mon Sep 17 00:00:00 2001 From: John Blum Date: Wed, 28 Nov 2018 14:29:06 -0800 Subject: [PATCH] Improve logic around resolving a Stack Trace (dump) for (test) tracing and debugging purposes. Include support for generating a universal trace identifier (UTID). Include support to conditionally enabling and disabling tracing. Fix incorrect usage of StackTraceElement.getClass(). Add public methods to resolve the class type and method in a StackTraceElement. --- .../gemfire/tests/util/StackTraceUtils.java | 152 +++++++++++++++--- 1 file changed, 130 insertions(+), 22 deletions(-) diff --git a/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/util/StackTraceUtils.java b/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/util/StackTraceUtils.java index 2f4e166..547e7c6 100644 --- a/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/util/StackTraceUtils.java +++ b/spring-data-geode-test/src/main/java/org/springframework/data/gemfire/tests/util/StackTraceUtils.java @@ -16,6 +16,21 @@ package org.springframework.data.gemfire.tests.util; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.lang.reflect.Method; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; + /** * The {@link StackTraceUtils} class is a utility class for working with stack trace frames (elements) * of the current {@link Thread}. @@ -29,56 +44,149 @@ package org.springframework.data.gemfire.tests.util; @SuppressWarnings("unused") public abstract class StackTraceUtils extends ThreadUtils { - public static StackTraceElement getCaller() { + private static final AtomicBoolean tracingEnabled = new AtomicBoolean(false); + + public static @NonNull String getUniversalTraceIdentifier() { + + String id = UUID.randomUUID().toString(); + String[] idElements = id.split("-"); + + return String.format("%s%s%s", idElements[0].substring(0, 3), idElements[3], + idElements[idElements.length - 1].substring(idElements[idElements.length - 1].length() - 3)); + } + + public static @NonNull StackTraceElement getCaller() { return getCaller(Thread.currentThread()); } - public static StackTraceElement getCaller(final Thread thread) { + public static @NonNull StackTraceElement getCaller(@NonNull Thread thread) { return thread.getStackTrace()[2]; } - public static String getCallerName(final StackTraceElement element) { - return String.format("%1$%s.%2$s", element.getClass().getName(), element.getMethodName()); + public static @NonNull String getCallerName(@NonNull StackTraceElement element) { + return String.format("%1$%s.%2$s", element.getClassName(), element.getMethodName()); } - public static String getCallerSimpleName(final StackTraceElement element) { - return String.format("%1$%s.%2$s", element.getClass().getSimpleName(), element.getMethodName()); + public static @NonNull String getCallerSimpleName(@NonNull StackTraceElement element) { + + String resolvedClassName = safeResolveClass(element) + .map(Class::getSimpleName) + .orElseGet(() -> { + + String className = element.getClassName(); + int index = element.getClassName().lastIndexOf("."); + + return index > -1 ? className.substring(index) : className; + }); + + return String.format("%1$%s.%2$s", resolvedClassName, element.getMethodName()); } - public static StackTraceElement getTestCaller() { + public static @Nullable StackTraceElement getTestCaller() { return getTestCaller(Thread.currentThread()); } - public static StackTraceElement getTestCaller(final Thread thread) { + public static @Nullable StackTraceElement getTestCaller(@NonNull Thread thread) { - for (StackTraceElement stackTraceElement : thread.getStackTrace()) { - if (isTestSuiteClass(stackTraceElement) && isTestCaseMethod(stackTraceElement)) { - return stackTraceElement; - } - } - - return null; + return Arrays.stream(thread.getStackTrace()) + .filter(StackTraceUtils::isTestSuiteClass) + .filter(StackTraceUtils::isTestCaseMethod) + .findFirst() + .orElse(null); } - private static boolean isTestCaseMethod(final StackTraceElement element) { + private static boolean isTestCaseMethod(StackTraceElement element) { boolean result = element.getMethodName().toLowerCase().startsWith("test"); try { - result |= element.getClass().getMethod(element.getMethodName()).isAnnotationPresent(org.junit.Test.class); - } - catch (NoSuchMethodException ignore) { + result |= resolveMethod(element).isAnnotationPresent(org.junit.Test.class); } + catch (ClassNotFoundException | NoSuchMethodException ignore) { } return result; } - private static boolean isTestSuiteClass(final StackTraceElement element) { + private static boolean isTestSuiteClass(StackTraceElement element) { - boolean result = element.getClass().getSimpleName().toLowerCase().endsWith("test"); + boolean result = element.getClassName().toLowerCase().endsWith("test"); - result |= element.getClass().isAssignableFrom(junit.framework.TestCase.class); + try { + result |= resolveClass(element).isAssignableFrom(junit.framework.TestCase.class); + } + catch (ClassNotFoundException ignore) { } return result; } + + public static @NonNull String getStackTrace() { + + StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace(); + + List resolvedStackTrace = Arrays.stream(stackTrace) + .filter(stackTraceElementFilter()) + .collect(Collectors.toList()); + + StringWriter writer = new StringWriter(); + + Throwable throwable = new Throwable("STACK TRACE DUMP"); + + throwable.setStackTrace(resolvedStackTrace.toArray(new StackTraceElement[resolvedStackTrace.size()])); + throwable.printStackTrace(new PrintWriter(writer)); + + return writer.toString(); + } + + private static Predicate stackTraceElementFilter() { + return element -> !StackTraceUtils.class.getName().equals(element.getClassName()); + } + + public static boolean isTracingEnabled() { + return tracingEnabled.get(); + } + + public static Class resolveClass(StackTraceElement element) throws ClassNotFoundException { + return Class.forName(element.getClassName()); + } + + public static Optional> safeResolveClass(StackTraceElement element) { + + try { + return Optional.of(resolveClass(element)); + } + catch (ClassNotFoundException ignore) { + return Optional.empty(); + } + } + + public static Method resolveMethod(StackTraceElement element) + throws ClassNotFoundException, NoSuchMethodException { + + return resolveClass(element).getMethod(element.getMethodName()); + } + + public static Optional safeResolveMethod(StackTraceElement element) { + + try { + return Optional.of(resolveMethod(element)); + } + catch (ClassNotFoundException | NoSuchMethodException ignore) { + return Optional.empty(); + } + } + + public static void whenTracingEnabled(@NonNull Consumer stackTraceConsumer) { + + if (isTracingEnabled()) { + stackTraceConsumer.accept(getStackTrace()); + } + } + + public void withTracing() { + tracingEnabled.set(true); + } + + public void withoutTracing() { + tracingEnabled.set(false); + } }