/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.codehaus.groovy.runtime; import groovy.lang.Closure; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Enumeration; import java.util.List; import java.util.logging.Level; import java.util.logging.LogManager; import java.util.logging.Logger; import org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation; /** * Originally was grails.utils.GrailsUtils, removed some grails specific stuff. * Utility methods removing internal lines from stack traces * * @author Graeme Rocher * @since 1.5 */ public class StackTraceUtils { public static final String STACK_LOG_NAME = "StackTrace"; private static final Logger STACK_LOG; // set log to consume traces by default, end user can override later static { outer: do { Enumeration existingLogs = LogManager.getLogManager().getLoggerNames(); while (existingLogs.hasMoreElements()) { if (STACK_LOG_NAME.equals(existingLogs.nextElement())) { STACK_LOG = Logger.getLogger(STACK_LOG_NAME); break outer; } } STACK_LOG = Logger.getLogger(STACK_LOG_NAME); STACK_LOG.setUseParentHandlers(false); } while (false); } private static final String[] GROOVY_PACKAGES = System.getProperty("groovy.sanitized.stacktraces", "groovy.," + "org.codehaus.groovy.," + "java.," + "javax.," + "sun.," + "gjdk.groovy.," ).split("(\\s|,)+"); private static final List<Closure> tests = new ArrayList<Closure>(); /** * Adds a groovy.lang.Closure to test whether the stack trace * element should be added or not. * <p> * The groovy.lang.Closure will be given the class name as parameter. * the return value decides if the element will be added or not. * <ul> * <li><b>true</b> - trace element will be added to the trace * <li><b>false</b> - trace element will <b>not</b> be added to the trace * <li><b>null</b> - continue with next test * </ul> * Groovy truth will be used to determine true and false, null is excluded from * defaulting to false here. If all tests have been executed and all of them skipped, then * the groovy standard filtering will take place. * * @param test the testing groovy.lang.Closure */ public static void addClassTest(Closure test) { tests.add(test); } /** * Remove all apparently groovy-internal trace entries from the exception instance * <p> * This modifies the original instance and returns it, it does not clone * * @param t the Throwable whose stack trace we want to sanitize * @return The original Throwable but with a sanitized stack trace */ public static Throwable sanitize(Throwable t) { // Note that this getBoolean access may well be synced... if (!Boolean.getBoolean("groovy.full.stacktrace")) { StackTraceElement[] trace = t.getStackTrace(); List<StackTraceElement> newTrace = new ArrayList<StackTraceElement>(); for (StackTraceElement stackTraceElement : trace) { if (isApplicationClass(stackTraceElement.getClassName())) { newTrace.add(stackTraceElement); } } // We don't want to lose anything, so log it STACK_LOG.log(Level.WARNING, "Sanitizing stacktrace:", t); StackTraceElement[] clean = new StackTraceElement[newTrace.size()]; newTrace.toArray(clean); t.setStackTrace(clean); } return t; } public static void printSanitizedStackTrace(Throwable t, PrintWriter p) { t = StackTraceUtils.sanitize(t); StackTraceElement[] trace = t.getStackTrace(); for (StackTraceElement stackTraceElement : trace) { p.println("at " + stackTraceElement.getClassName() + "(" + stackTraceElement.getMethodName() + ":" + stackTraceElement.getLineNumber() + ")"); } } public static void printSanitizedStackTrace(Throwable t) { printSanitizedStackTrace(t, new PrintWriter(System.err)); } public static boolean isApplicationClass(String className) { for (Closure test : tests) { Object result = test.call(className); if (result != null) { return DefaultTypeTransformation.castToBoolean(result); } } for (String groovyPackage : GROOVY_PACKAGES) { if (className.startsWith(groovyPackage)) { return false; } } return true; } /** * Extracts the root cause of the exception, no matter how nested it is * * @param t a Throwable * @return The deepest cause of the exception that can be found */ public static Throwable extractRootCause(Throwable t) { Throwable result = t; while (result.getCause() != null) { result = result.getCause(); } return result; } /** * Get the root cause of an exception and sanitize it for display to the user * <p> * This will MODIFY the stacktrace of the root cause exception object and return it * * @param t a throwable * @return The root cause exception instance, with its stace trace modified to filter out groovy runtime classes */ public static Throwable sanitizeRootCause(Throwable t) { return StackTraceUtils.sanitize(StackTraceUtils.extractRootCause(t)); } /** * Sanitize the exception and ALL nested causes * <p> * This will MODIFY the stacktrace of the exception instance and all its causes irreversibly * * @param t a throwable * @return The root cause exception instances, with stack trace modified to filter out groovy runtime classes */ public static Throwable deepSanitize(Throwable t) { Throwable current = t; while (current.getCause() != null) { current = StackTraceUtils.sanitize(current.getCause()); } return StackTraceUtils.sanitize(t); } }