From f6673cdb85879a7b385736fff2ae99aab78d7424 Mon Sep 17 00:00:00 2001 From: Philippe Marschall Date: Fri, 22 Sep 2023 16:42:04 +0200 Subject: [PATCH] Implement QueryStackTraceLogger using StackWalker #659 --- .../query/QueryStackTraceLogger.java | 83 +++++++++---------- .../query/QueryStackTraceLogger.java | 83 +++++++++---------- 2 files changed, 80 insertions(+), 86 deletions(-) diff --git a/hypersistence-utils-hibernate-60/src/main/java/io/hypersistence/utils/hibernate/query/QueryStackTraceLogger.java b/hypersistence-utils-hibernate-60/src/main/java/io/hypersistence/utils/hibernate/query/QueryStackTraceLogger.java index 34ce193b4..e7d708046 100644 --- a/hypersistence-utils-hibernate-60/src/main/java/io/hypersistence/utils/hibernate/query/QueryStackTraceLogger.java +++ b/hypersistence-utils-hibernate-60/src/main/java/io/hypersistence/utils/hibernate/query/QueryStackTraceLogger.java @@ -4,9 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; +import java.util.concurrent.atomic.AtomicBoolean; /** * The {@link QueryStackTraceLogger} allows you to log the stack trace that @@ -17,7 +15,7 @@ */ public class QueryStackTraceLogger implements StatementInspector { - public static final String ORG_HIBERNATE ="org.hibernate"; + public static final String ORG_HIBERNATE = "org.hibernate"; public static String TAB = "\t"; public static String NEW_LINE = System.getProperty("line.separator"); @@ -32,51 +30,50 @@ public QueryStackTraceLogger(String packageNamePrefix) { @Override public String inspect(String sql) { - LOGGER.debug( - "This SQL query: [\n\t{}\n]\nwas generated by Hibernate like this: [\n{}\n]", - sql, - String.join( - NEW_LINE, - stackTraceElementsUpTo(packageNamePrefix) - .stream() - .map(e -> TAB + e.toString()) - .collect(Collectors.toList()) - ) - ); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(stackTraceStringUpTo(packageNamePrefix, sql)); + } return null; } /** - * Filter the stack trace based up to the provided package name prefix + * Generate a formatted stack trace string based up to the provided package name prefix. * * @param endPackageNamePrefix package name to match the {@link StackTraceElement} - * @return the {@link StackTraceElement} up to the matching the provided package name + * @param query the query string to embed in the log message + * @return the stack trace {@link String} up to the matching the provided package name */ - private List stackTraceElementsUpTo(String endPackageNamePrefix) { - StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); - List filteredStackTraceElements = new ArrayList<>(); - boolean startPackageMatched = false; - boolean endPackageMatched = false; - for (StackTraceElement stackTraceElement : stackTraceElements) { - String className = stackTraceElement.getClassName(); - if(!startPackageMatched) { - if(className.startsWith(ORG_HIBERNATE)) { - startPackageMatched = true; - } else { - continue; - } - } - if(!className.contains(endPackageNamePrefix)) { - if(!endPackageMatched) { - filteredStackTraceElements.add(stackTraceElement); - } else { - break; - } - } else if(!endPackageMatched) { - endPackageMatched = true; - filteredStackTraceElements.add(stackTraceElement); - } - } - return filteredStackTraceElements; + private static String stackTraceStringUpTo(String endPackageNamePrefix, String query) { + return StackWalker.getInstance().walk(stream -> { + StringBuilder buffer = new StringBuilder(); + buffer.append("This SQL query: [\n\t"); + buffer.append(query); + buffer.append("\n]\n"); + buffer.append("was generated by Hibernate like this: [\n"); + AtomicBoolean firstMatch = new AtomicBoolean(false); + stream + .skip(2L) // skip this and the calling method + .dropWhile(stackFrame -> // skip anything else until we end up in Hibernate + !stackFrame.getClassName().startsWith(ORG_HIBERNATE)) + .takeWhile(stackFrame -> { + // take anything up to and including the first method in a class in endPackageNamePrefix + // would #startsWith be more appropriate? + if (stackFrame.getClassName().contains(endPackageNamePrefix)) { + if (firstMatch.get()) { + return false; + } else { + firstMatch.set(true); + return true; + } + } else { + return !firstMatch.get(); + } + }) + .forEach(stackFrame -> { + buffer.append(TAB).append(stackFrame.toStackTraceElement()).append(NEW_LINE); + }); + buffer.append(']'); + return buffer.toString(); + }); } } diff --git a/hypersistence-utils-hibernate-62/src/main/java/io/hypersistence/utils/hibernate/query/QueryStackTraceLogger.java b/hypersistence-utils-hibernate-62/src/main/java/io/hypersistence/utils/hibernate/query/QueryStackTraceLogger.java index 34ce193b4..e7d708046 100644 --- a/hypersistence-utils-hibernate-62/src/main/java/io/hypersistence/utils/hibernate/query/QueryStackTraceLogger.java +++ b/hypersistence-utils-hibernate-62/src/main/java/io/hypersistence/utils/hibernate/query/QueryStackTraceLogger.java @@ -4,9 +4,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; +import java.util.concurrent.atomic.AtomicBoolean; /** * The {@link QueryStackTraceLogger} allows you to log the stack trace that @@ -17,7 +15,7 @@ */ public class QueryStackTraceLogger implements StatementInspector { - public static final String ORG_HIBERNATE ="org.hibernate"; + public static final String ORG_HIBERNATE = "org.hibernate"; public static String TAB = "\t"; public static String NEW_LINE = System.getProperty("line.separator"); @@ -32,51 +30,50 @@ public QueryStackTraceLogger(String packageNamePrefix) { @Override public String inspect(String sql) { - LOGGER.debug( - "This SQL query: [\n\t{}\n]\nwas generated by Hibernate like this: [\n{}\n]", - sql, - String.join( - NEW_LINE, - stackTraceElementsUpTo(packageNamePrefix) - .stream() - .map(e -> TAB + e.toString()) - .collect(Collectors.toList()) - ) - ); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug(stackTraceStringUpTo(packageNamePrefix, sql)); + } return null; } /** - * Filter the stack trace based up to the provided package name prefix + * Generate a formatted stack trace string based up to the provided package name prefix. * * @param endPackageNamePrefix package name to match the {@link StackTraceElement} - * @return the {@link StackTraceElement} up to the matching the provided package name + * @param query the query string to embed in the log message + * @return the stack trace {@link String} up to the matching the provided package name */ - private List stackTraceElementsUpTo(String endPackageNamePrefix) { - StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace(); - List filteredStackTraceElements = new ArrayList<>(); - boolean startPackageMatched = false; - boolean endPackageMatched = false; - for (StackTraceElement stackTraceElement : stackTraceElements) { - String className = stackTraceElement.getClassName(); - if(!startPackageMatched) { - if(className.startsWith(ORG_HIBERNATE)) { - startPackageMatched = true; - } else { - continue; - } - } - if(!className.contains(endPackageNamePrefix)) { - if(!endPackageMatched) { - filteredStackTraceElements.add(stackTraceElement); - } else { - break; - } - } else if(!endPackageMatched) { - endPackageMatched = true; - filteredStackTraceElements.add(stackTraceElement); - } - } - return filteredStackTraceElements; + private static String stackTraceStringUpTo(String endPackageNamePrefix, String query) { + return StackWalker.getInstance().walk(stream -> { + StringBuilder buffer = new StringBuilder(); + buffer.append("This SQL query: [\n\t"); + buffer.append(query); + buffer.append("\n]\n"); + buffer.append("was generated by Hibernate like this: [\n"); + AtomicBoolean firstMatch = new AtomicBoolean(false); + stream + .skip(2L) // skip this and the calling method + .dropWhile(stackFrame -> // skip anything else until we end up in Hibernate + !stackFrame.getClassName().startsWith(ORG_HIBERNATE)) + .takeWhile(stackFrame -> { + // take anything up to and including the first method in a class in endPackageNamePrefix + // would #startsWith be more appropriate? + if (stackFrame.getClassName().contains(endPackageNamePrefix)) { + if (firstMatch.get()) { + return false; + } else { + firstMatch.set(true); + return true; + } + } else { + return !firstMatch.get(); + } + }) + .forEach(stackFrame -> { + buffer.append(TAB).append(stackFrame.toStackTraceElement()).append(NEW_LINE); + }); + buffer.append(']'); + return buffer.toString(); + }); } }