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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user