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