package biz.paluch.logging; import static biz.paluch.logging.RuntimeContainerProperties.getProperty; import java.io.*; import java.util.*; import java.util.regex.Pattern; import biz.paluch.logging.gelf.intern.Closer; /** * Filtering Facility for stack traces. This is to shorten very long Traces. It creates a redacted trace containing only the * interesting parts. Please provide an own Resource {@code /StackTraceFilter.packages} with the package prefixes if you want to * use a custom filter (one package per line). * * <code> # Packages to filter org.h2 org.apache.catalina org.apache.coyote org.apache.tomcat com.arjuna org.apache.cxf * </code> * * Original stack trace: <code>java.lang.RuntimeException: entryMethod at biz.paluch.logging.StackTraceFilterTest.entryMethod(StackTraceFilterTest.java:49) at biz.paluch.logging.StackTraceFilterTest.printStackTrace(StackTraceFilterTest.java:35) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:47) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:44) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:271) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:70) at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:50) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:238) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:63) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:236) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:53) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:229) at org.junit.runners.ParentRunner.run(ParentRunner.java:309) at org.junit.runner.JUnitCore.run(JUnitCore.java:160) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:497) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) Caused by: biz.paluch.logging.StackTraceFilterTest$MyException: Intermediate 2 at biz.paluch.logging.StackTraceFilterTest.intermediate2(StackTraceFilterTest.java:58) at biz.paluch.logging.StackTraceFilterTest.intermediate1(StackTraceFilterTest.java:53) ... 30 more Suppressed: java.lang.RuntimeException: surpressed at biz.paluch.logging.StackTraceFilterTest.intermediate2(StackTraceFilterTest.java:59) ... 31 more Suppressed: java.lang.RuntimeException: surpressed at biz.paluch.logging.StackTraceFilterTest.intermediate2(StackTraceFilterTest.java:60) ... 31 more Suppressed: java.lang.IllegalArgumentException: Failed to parse byte. at org.jboss.common.beans.property.ByteEditor.setAsText(ByteEditor.java:48) at biz.paluch.logging.StackTraceFilterTest.intermediate2(StackTraceFilterTest.java:65) ... 31 more Caused by: java.lang.NumberFormatException: For input string: "adsd" at java.lang.NumberFormatException.forInputString(NumberFormatException.java:65) at java.lang.Integer.parseInt(Integer.java:580) at java.lang.Integer.valueOf(Integer.java:740) at java.lang.Integer.decode(Integer.java:1197) at java.lang.Byte.decode(Byte.java:277) at org.jboss.common.beans.property.ByteEditor.setAsText(ByteEditor.java:45) ... 32 more Caused by: biz.paluch.logging.StackTraceFilterTest$MyException: Message at biz.paluch.logging.StackTraceFilterTest.intermediate3(StackTraceFilterTest.java:77) ... 32 more Suppressed: java.lang.IllegalStateException: Some illegal state at biz.paluch.logging.StackTraceFilterTest.cause(StackTraceFilterTest.java:84) at biz.paluch.logging.StackTraceFilterTest.intermediate3(StackTraceFilterTest.java:78) ... 32 more Suppressed: java.lang.IllegalStateException: Some illegal state at biz.paluch.logging.StackTraceFilterTest.cause(StackTraceFilterTest.java:84) at biz.paluch.logging.StackTraceFilterTest.intermediate3(StackTraceFilterTest.java:79) ... 32 more Caused by: java.lang.IllegalStateException: Some illegal state at biz.paluch.logging.StackTraceFilterTest.cause(StackTraceFilterTest.java:84) ... 33 more </code> * * Filtered stack trace: <code>java.lang.RuntimeException: entryMethod at biz.paluch.logging.StackTraceFilterTest.entryMethod(StackTraceFilterTest.java:49) at biz.paluch.logging.StackTraceFilterTest.filterWholeStackTrace(StackTraceFilterTest.java:43) 19 lines skipped for [java.lang, sun., org.junit] at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) 4 lines skipped for [java.lang, sun.] at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) Caused by: biz.paluch.logging.StackTraceFilterTest$MyException: Intermediate 2 at biz.paluch.logging.StackTraceFilterTest.intermediate2(StackTraceFilterTest.java:58) at biz.paluch.logging.StackTraceFilterTest.intermediate1(StackTraceFilterTest.java:53) ... 30 more Suppressed: java.lang.RuntimeException: surpressed at biz.paluch.logging.StackTraceFilterTest.intermediate2(StackTraceFilterTest.java:59) ... 31 more Suppressed: java.lang.RuntimeException: surpressed at biz.paluch.logging.StackTraceFilterTest.intermediate2(StackTraceFilterTest.java:60) ... 31 more Suppressed: java.lang.IllegalArgumentException: Failed to parse byte. at org.jboss.common.beans.property.ByteEditor.setAsText(ByteEditor.java:48) at biz.paluch.logging.StackTraceFilterTest.intermediate2(StackTraceFilterTest.java:65) ... 31 more Caused by: java.lang.NumberFormatException: For input string: "adsd" 6 lines skipped for [java.lang, org.jboss] ... 32 more Caused by: biz.paluch.logging.StackTraceFilterTest$MyException: Message at biz.paluch.logging.StackTraceFilterTest.intermediate3(StackTraceFilterTest.java:77) ... 32 more Suppressed: java.lang.IllegalStateException: Some illegal state at biz.paluch.logging.StackTraceFilterTest.cause(StackTraceFilterTest.java:84) at biz.paluch.logging.StackTraceFilterTest.intermediate3(StackTraceFilterTest.java:78) ... 32 more Suppressed: java.lang.IllegalStateException: Some illegal state at biz.paluch.logging.StackTraceFilterTest.cause(StackTraceFilterTest.java:84) at biz.paluch.logging.StackTraceFilterTest.intermediate3(StackTraceFilterTest.java:79) ... 32 more Caused by: java.lang.IllegalStateException: Some illegal state at biz.paluch.logging.StackTraceFilterTest.cause(StackTraceFilterTest.java:84) ... 33 more</code> * * @author Mark Paluch */ public class StackTraceFilter { public static final String VERBOSE_LOGGING_PROPERTY = "logstash-gelf.StackTraceFilter.verbose"; public static final String FILTER_SETTINGS = "/" + StackTraceFilter.class.getSimpleName() + ".packages"; private static final String INDENT = "\t"; private static final boolean VERBOSE_LOGGING = Boolean.parseBoolean(getProperty(VERBOSE_LOGGING_PROPERTY, "false")); private static final Pattern AT_PATTERN = Pattern.compile("(" + INDENT + ")+at"); private static final Pattern SURPRESSED_PATTERN = Pattern.compile("(" + INDENT + ")+Suppressed\\:"); /** * List of suppressed Packages. */ private static Set<String> suppressedPackages; static { loadSetttings(FILTER_SETTINGS); } public static void loadSetttings(String resourceName) { InputStream is = null; try { is = getStream(resourceName); if (is == null) { verboseLog("No " + resourceName + " resource present, using defaults"); suppressedPackages = new HashSet<String>(getDefaults()); } else { Properties p = new Properties(); p.load(is); suppressedPackages = (Set) p.keySet(); } } catch (IOException e) { verboseLog("Could not parse " + resourceName + " resource, using defaults"); suppressedPackages = new HashSet<String>(getDefaults()); } finally { Closer.close(is); } } private static InputStream getStream(String resourceName) { Thread thread = Thread.currentThread(); InputStream is = StackTraceFilter.class.getResourceAsStream(resourceName); if (is == null && thread.getContextClassLoader() != null) { is = thread.getContextClassLoader().getResourceAsStream(resourceName); } return is; } public static List<String> getDefaults() { return Arrays.asList("org.h2", "org.apache.catalina", "org.apache.coyote", "org.apache.tomcat", "com.arjuna", "org.apache.cxf", "org.hibernate", "org.junit", "org.jboss", "java.lang.reflect.Method", "sun.", "com.sun", "org.eclipse", "junit.framework", "com.sun.faces", "javax.faces", "org.richfaces", "org.apache.el", "javax.servlet"); } /** * Utility-Constructor. */ private StackTraceFilter() { } /** * Get a filterered stack trace. * * @param t the throwable * @return String containing the filtered stack trace. */ public static String getFilteredStackTrace(Throwable t) { return getFilteredStackTrace(t, true); } /** * Get a filterered stack trace. * * @param t the throwable * @param shouldFilter true in case filtering should be performed. Else stack trace as string will be returned. * @return String containing the stack trace. * @deprecated since 1.11.1. Use {@link #getStackTrace(Throwable, int)} to get the stack trace without filtering or * {@link #getFilteredStackTrace(Throwable)} to get the filtered the stack trace. */ @Deprecated public static String getFilteredStackTrace(Throwable t, boolean shouldFilter) { if (shouldFilter) { return getFilteredStackTrace(t, 0); } StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); t.printStackTrace(pw); pw.close(); return sw.getBuffer().toString(); } /** * Filter stack trace by selecting the {@link Throwable} using a reference position. Intermediate throwables will be printed * with just their header. * * @param t the throwable * @param ref throwable reference position, see {@link #getThrowable(List, int)}. * @return String containing the stack trace. * @since 1.11.1 */ public static String getFilteredStackTrace(Throwable t, int ref) { StringWriter sw = new StringWriter(); FilterWriter filterWriter = new StackTraceFilterWriter(sw); printStackTrace(t, filterWriter, ref); return sw.getBuffer().toString(); } /** * Get the {@link Throwable}'s stack trace. * * @param t the throwable * @return String containing the stack trace. * @since 1.11.1 */ public static String getStackTrace(Throwable t) { return getStackTrace(t, 0); } /** * Get the stack trace by selecting the {@link Throwable} using a reference position. Intermediate throwables will be * printed with just their header. * * @param t the throwable * @param ref throwable reference position, see {@link #getThrowable(List, int)}. * @return String containing the stack trace. * @since 1.11.1 */ public static String getStackTrace(Throwable t, int ref) { StringWriter sw = new StringWriter(); printStackTrace(t, sw, ref); return sw.getBuffer().toString(); } private static void printStackTrace(Throwable t, Writer writer, int ref) { List<Throwable> throwables = getThrowables(t); PrintWriter exceptionWriter = new PrintWriter(writer); Throwable subject = ref == 0 ? throwables.get(ref) : getThrowable(throwables, ref); boolean first = true; for (Throwable throwable : throwables) { if (subject == throwable) { break; } if (!first) { exceptionWriter.print("Caused by: "); } first = false; exceptionWriter.print(throwable.toString()); exceptionWriter.println(); } if (ref != 0) { exceptionWriter.print("Caused by: "); } subject.printStackTrace(exceptionWriter); } /** * Return a {@link Throwable} by its reference position. * <p> * This method extracts a {@link Throwable} from the exception chain of {@link Throwable#getCause() causes}. A reference of * {@code 0} returns the original {@link Throwable}, values greater zero will walk the cause chain up so a reference * {@code 1} returns {@code t.getCause()}. Negative reference values will walk the causing exception from the root cause * side. A reference of {@code -1} returns the root cause, {@code -2} the exception that wraps the root cause and so on. * * @param throwable the caught {@link Throwable}. * @param ref reference position * @return the selected {@link Throwable}. */ public static Throwable getThrowable(Throwable throwable, int ref) { return getThrowable(getThrowables(throwable), ref); } private static Throwable getThrowable(List<Throwable> throwables, int ref) { if (ref >= 0) { return throwables.get(Math.min(ref, throwables.size() - 1)); } return throwables.get(Math.max(throwables.size() + ref, 0)); } private static List<Throwable> getThrowables(Throwable t) { List<Throwable> throwables = new ArrayList<Throwable>(); Throwable current = t; do { throwables.add(current); current = current.getCause(); } while (current != null && !throwables.contains(current)); return throwables; } private static int getIndentation(String traceElement) { int index = traceElement.indexOf(INDENT); int indentationLevel = 0; while (index != -1) { indentationLevel++; index = traceElement.indexOf(INDENT, index + 1); } return indentationLevel; } // 37 lines skipped for [org.h2, org.hibernate, sun., // java.lang.reflect.Method, $Proxy] private static String getSkippedPackagesMessage(Set<String> skippedPackages, int skippedLines, int indentationLevel) { StringBuilder stringBuilder = new StringBuilder(); for (int i = 0; i < 2 + indentationLevel; i++) { stringBuilder.append(INDENT); } stringBuilder.append(skippedLines).append(" line").append((skippedLines == 1 ? "" : "s")).append(" skipped for ") .append(skippedPackages); return stringBuilder.toString(); } /** * Checks to see if the class is part of a forbidden package. If so, it returns the package name from the list of suppressed * packages that matches, otherwise it returns null. * * @param classAndMethod StackTraceElement * @return forbidden package name or null. */ private static String tryGetForbiddenPackageName(String classAndMethod) { for (String pkg : suppressedPackages) { if (classAndMethod.startsWith(pkg)) { return pkg; } } return null; } private static void verboseLog(String message) { if (VERBOSE_LOGGING) { System.out.println(message); } } static class StackTraceFilterWriter extends FilterWriter { private final String traceElementPrefix = INDENT + "at "; Set<String> skippedPackages = new HashSet<String>(); int skippedLines; boolean endsWithNewLine; boolean first = true; int indentationLevel; private final String lineSeparator = System.getProperty("line.separator"); public StackTraceFilterWriter(Writer s) { super(s); } @Override public void write(String str, int off, int len) throws IOException { String toWrite = str.substring(off, len); if (skippedLines > 0 && toWrite.equals(lineSeparator) && endsWithNewLine) { return; } if (toWrite.equals(lineSeparator)) { endsWithNewLine = true; super.write(str, off, len); return; } if (SURPRESSED_PATTERN.matcher(toWrite).find()) { first = true; } if (endsWithNewLine && AT_PATTERN.matcher(toWrite).find()) { String traceElement = getTraceElement(toWrite); String forbiddenPackageName = null; if (!first) { forbiddenPackageName = tryGetForbiddenPackageName(traceElement); } first = false; if (forbiddenPackageName == null) { afterFiltering(); super.write(str, off, len); } else { indentationLevel = getIndentation(toWrite); skippedLines++; skippedPackages.add(forbiddenPackageName); } return; } afterFiltering(); super.write(str, off, len); endsWithNewLine = str.equals(lineSeparator); } private void afterFiltering() throws IOException { if (!skippedPackages.isEmpty()) { // 37 lines skipped for [org.h2, org.hibernate, sun., // java.lang.reflect.Method, $Proxy] String skippedPackagesMessage = getSkippedPackagesMessage(skippedPackages, skippedLines, indentationLevel); skippedPackages.clear(); skippedLines = 0; write(skippedPackagesMessage); write(lineSeparator); // at hib.HibExample.test(HibExample.java:18) indentationLevel = 0; } } private String getTraceElement(String toWrite) { return toWrite.substring(toWrite.indexOf(traceElementPrefix) + traceElementPrefix.length()); } @Override public void close() throws IOException { if (skippedLines > 0) { write(getSkippedPackagesMessage(skippedPackages, skippedLines, indentationLevel)); } super.close(); } } }