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.
This commit is contained in:
John Blum
2018-11-28 14:29:06 -08:00
parent 2a16cc037e
commit 76f11970f5

View File

@@ -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<StackTraceElement> 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<StackTraceElement> 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<Class<?>> 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<Method> safeResolveMethod(StackTraceElement element) {
try {
return Optional.of(resolveMethod(element));
}
catch (ClassNotFoundException | NoSuchMethodException ignore) {
return Optional.empty();
}
}
public static void whenTracingEnabled(@NonNull Consumer<String> stackTraceConsumer) {
if (isTracingEnabled()) {
stackTraceConsumer.accept(getStackTrace());
}
}
public void withTracing() {
tracingEnabled.set(true);
}
public void withoutTracing() {
tracingEnabled.set(false);
}
}