001 /*
002 * Copyright 2008-2017 the original author or authors.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016 package griffon.core;
017
018 import org.slf4j.Logger;
019 import org.slf4j.LoggerFactory;
020
021 import javax.annotation.Nullable;
022 import java.util.ArrayList;
023 import java.util.List;
024 import java.util.Map;
025
026 import static griffon.util.GriffonNameUtils.getShortName;
027 import static java.util.Arrays.asList;
028
029 /**
030 * Catches and sanitizes all uncaught exceptions.
031 *
032 * @author Danno Ferrin
033 * @author Andres Almiray
034 */
035 public class GriffonExceptionHandler implements ExceptionHandler {
036 private static final Logger LOG = LoggerFactory.getLogger(GriffonExceptionHandler.class);
037
038 private static final String[] CONFIG_OPTIONS = {
039 GRIFFON_FULL_STACKTRACE,
040 GRIFFON_EXCEPTION_OUTPUT
041 };
042
043 private static final String[] GRIFFON_PACKAGES =
044 System.getProperty("griffon.sanitized.stacktraces",
045 "org.codehaus.groovy.," +
046 "org.codehaus.griffon.," +
047 "groovy.," +
048 "java.," +
049 "javax.," +
050 "sun.," +
051 "com.sun.,"
052 ).split("(\\s|,)+");
053
054 private static final List<CallableWithArgs<Boolean>> TESTS = new ArrayList<>();
055
056 private static final String SANITIZED_STACKTRACE_MSG = "Stacktrace was sanitized. Set System property '" + GRIFFON_FULL_STACKTRACE + "' to 'true' for full report.";
057
058 public static void addClassTest(CallableWithArgs<Boolean> test) {
059 TESTS.add(test);
060 }
061
062 private GriffonApplication application;
063
064 public GriffonExceptionHandler() {
065 Thread.setDefaultUncaughtExceptionHandler(this);
066 }
067
068 // @Inject
069 public GriffonExceptionHandler(GriffonApplication application) {
070 this.application = application;
071 Thread.setDefaultUncaughtExceptionHandler(this);
072 }
073
074 @Nullable
075 protected GriffonApplication getApplication() {
076 return application;
077 }
078
079 @Override
080 public void uncaughtException(Thread t, Throwable e) {
081 handle(e);
082 }
083
084 @Override
085 @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
086 public void handle(Throwable throwable) {
087 try {
088 sanitize(throwable);
089 printStacktrace(throwable);
090 logError("Uncaught Exception.", throwable);
091 if (application != null) {
092 application.getEventRouter().publishEvent("Uncaught" + getShortName(throwable.getClass()), asList(throwable));
093 application.getEventRouter().publishEvent(ApplicationEvent.UNCAUGHT_EXCEPTION_THROWN.getName(), asList(throwable));
094 }
095 } catch (Throwable t) {
096 sanitize(t);
097 printStacktrace(t);
098 logError("An error occurred while handling uncaught exception " + throwable + ".", t);
099 }
100 }
101
102 @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
103 public static Throwable sanitize(Throwable throwable) {
104 try {
105 if (!isFullStacktraceEnabled()) {
106 deepSanitize(throwable);
107 }
108 } catch (Throwable t) {
109 // don't let the exception get thrown out, will cause infinite looping!
110 }
111 return throwable;
112 }
113
114 @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
115 public static StackTraceElement[] sanitize(StackTraceElement[] stackTrace) {
116 try {
117 if (!isFullStacktraceEnabled()) {
118 Throwable t = new Throwable();
119 t.setStackTrace(stackTrace);
120 sanitize(t);
121 stackTrace = t.getStackTrace();
122 }
123 } catch (Throwable o) {
124 // don't let the exception get thrown out, will cause infinite looping!
125 }
126 return stackTrace;
127 }
128
129 public static boolean isOutputEnabled() {
130 return Boolean.getBoolean(GRIFFON_EXCEPTION_OUTPUT);
131 }
132
133 public static void configure(Map<String, Object> config) {
134 for (String option : CONFIG_OPTIONS) {
135 if (config.containsKey(option)) {
136 System.setProperty(option, String.valueOf(config.get(option)));
137 }
138 }
139 }
140
141 public static void registerExceptionHandler() {
142 Thread.setDefaultUncaughtExceptionHandler(new GriffonExceptionHandler());
143 System.setProperty("sun.awt.exception.handler", GriffonExceptionHandler.class.getName());
144 }
145
146 public static void handleThrowable(Throwable t) {
147 Thread.getDefaultUncaughtExceptionHandler().uncaughtException(
148 Thread.currentThread(),
149 t
150 );
151 }
152
153 /**
154 * Sanitize the exception and ALL nested causes
155 * <p/>
156 * This will MODIFY the stacktrace of the exception instance and all its causes irreversibly
157 *
158 * @param t a throwable
159 *
160 * @return The root cause exception instances, with stack trace modified to filter out groovy runtime classes
161 */
162 @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
163 public static Throwable deepSanitize(Throwable t) {
164 Throwable current = t;
165 while (current.getCause() != null) {
166 current = doSanitize(current.getCause());
167 }
168 return doSanitize(t);
169 }
170
171 private static Throwable doSanitize(Throwable t) {
172 StackTraceElement[] trace = t.getStackTrace();
173 List<StackTraceElement> newTrace = new ArrayList<>();
174 for (StackTraceElement stackTraceElement : trace) {
175 if (isApplicationClass(stackTraceElement.getClassName())) {
176 newTrace.add(stackTraceElement);
177 }
178 }
179
180 StackTraceElement[] clean = new StackTraceElement[newTrace.size()];
181 newTrace.toArray(clean);
182 t.setStackTrace(clean);
183 return t;
184 }
185
186 public static boolean isFullStacktraceEnabled() {
187 return Boolean.getBoolean(GRIFFON_FULL_STACKTRACE);
188 }
189
190 private static void printStacktrace(Throwable throwable) {
191 if (isOutputEnabled()) {
192 if (!isFullStacktraceEnabled()) {
193 System.err.println(SANITIZED_STACKTRACE_MSG);
194 }
195 throwable.printStackTrace(System.err);
196 }
197 }
198
199 private static void logError(String message, Throwable throwable) {
200 if (!isFullStacktraceEnabled()) {
201 message += " " + SANITIZED_STACKTRACE_MSG;
202 }
203 LOG.error(message, throwable);
204 }
205
206 private static boolean isApplicationClass(String className) {
207 for (CallableWithArgs<Boolean> test : TESTS) {
208 if (test.call(className)) {
209 return false;
210 }
211 }
212
213 for (String excludedPackage : GRIFFON_PACKAGES) {
214 if (className.startsWith(excludedPackage)) {
215 return false;
216 }
217 }
218 return true;
219 }
220 }
|