You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ErrorManager.java 16KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. /*
  2. * Copyright (c) 2006-2014 DMDirc Developers
  3. *
  4. * Permission is hereby granted, free of charge, to any person obtaining a copy
  5. * of this software and associated documentation files (the "Software"), to deal
  6. * in the Software without restriction, including without limitation the rights
  7. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. * copies of the Software, and to permit persons to whom the Software is
  9. * furnished to do so, subject to the following conditions:
  10. *
  11. * The above copyright notice and this permission notice shall be included in
  12. * all copies or substantial portions of the Software.
  13. *
  14. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  20. * SOFTWARE.
  21. */
  22. package com.dmdirc.logger;
  23. import com.dmdirc.DMDircMBassador;
  24. import com.dmdirc.events.AppErrorEvent;
  25. import com.dmdirc.events.UserErrorEvent;
  26. import com.dmdirc.interfaces.config.AggregateConfigProvider;
  27. import com.dmdirc.interfaces.config.ConfigChangeListener;
  28. import com.dmdirc.ui.FatalErrorDialog;
  29. import com.dmdirc.util.ClientInfo;
  30. import com.dmdirc.util.collections.ListenerList;
  31. import java.awt.GraphicsEnvironment;
  32. import java.nio.file.Path;
  33. import java.util.Date;
  34. import java.util.LinkedList;
  35. import java.util.List;
  36. import java.util.concurrent.BlockingQueue;
  37. import java.util.concurrent.LinkedBlockingQueue;
  38. import java.util.concurrent.atomic.AtomicLong;
  39. import net.engio.mbassy.listener.Handler;
  40. import net.kencochrane.raven.DefaultRavenFactory;
  41. import net.kencochrane.raven.RavenFactory;
  42. /**
  43. * Error manager.
  44. */
  45. public class ErrorManager implements ConfigChangeListener {
  46. /** Previously instantiated instance of ErrorManager. */
  47. private static ErrorManager me;
  48. /** A list of exceptions which we don't consider bugs and thus don't report. */
  49. private static final Class<?>[] BANNED_EXCEPTIONS = new Class<?>[]{
  50. NoSuchMethodError.class, NoClassDefFoundError.class,
  51. UnsatisfiedLinkError.class, AbstractMethodError.class,
  52. IllegalAccessError.class, OutOfMemoryError.class,
  53. NoSuchFieldError.class,};
  54. /** Whether or not to send error reports. */
  55. private boolean sendReports;
  56. /** Whether or not to log error reports. */
  57. private boolean logReports;
  58. /** Queue of errors to be reported. */
  59. private final BlockingQueue<ProgramError> reportQueue = new LinkedBlockingQueue<>();
  60. /** Thread used for sending errors. */
  61. private volatile Thread reportThread;
  62. /** Error list. */
  63. private final List<ProgramError> errors;
  64. /** Listener list. */
  65. private final ListenerList errorListeners = new ListenerList();
  66. /** Next error ID. */
  67. private final AtomicLong nextErrorID;
  68. /** Config to read settings from. */
  69. private AggregateConfigProvider config;
  70. /** Directory to store errors in. */
  71. private Path errorsDirectory;
  72. private ClientInfo clientInfo;
  73. /** Creates a new instance of ErrorListDialog. */
  74. public ErrorManager() {
  75. errors = new LinkedList<>();
  76. nextErrorID = new AtomicLong();
  77. }
  78. /**
  79. * Initialises the error manager.
  80. *
  81. * @param globalConfig The configuration to read settings from.
  82. * @param directory The directory to store errors in, if enabled.
  83. * @param eventBus The event bus to listen for error events on.
  84. */
  85. public void initialise(final AggregateConfigProvider globalConfig, final Path directory,
  86. final DMDircMBassador eventBus, final ClientInfo clientInfo) {
  87. this.clientInfo = clientInfo;
  88. eventBus.subscribe(this);
  89. RavenFactory.registerFactory(new DefaultRavenFactory());
  90. config = globalConfig;
  91. config.addChangeListener("general", "logerrors", this);
  92. config.addChangeListener("general", "submitErrors", this);
  93. config.addChangeListener("temp", "noerrorreporting", this);
  94. updateSettings();
  95. errorsDirectory = directory;
  96. // Loop through any existing errors and send/save them per the config.
  97. for (ProgramError error : errors) {
  98. if (sendReports && error.getReportStatus() == ErrorReportStatus.WAITING) {
  99. sendError(error);
  100. }
  101. if (logReports) {
  102. error.save(errorsDirectory);
  103. }
  104. }
  105. }
  106. /**
  107. * Returns the instance of ErrorManager.
  108. *
  109. * @return Instance of ErrorManager
  110. */
  111. public static synchronized ErrorManager getErrorManager() {
  112. if (me == null) {
  113. me = new ErrorManager();
  114. }
  115. return me;
  116. }
  117. /**
  118. * Sets the singleton instance of the error manager.
  119. *
  120. * @param errorManager The error manager to use.
  121. */
  122. public static void setErrorManager(final ErrorManager errorManager) {
  123. me = errorManager;
  124. }
  125. @Handler
  126. public void handleAppErrorEvent(final AppErrorEvent appError) {
  127. addError(appError.getLevel(), appError.getMessage(), appError.getThrowable(),
  128. appError.getDetails(), true, isValidError(appError.getThrowable()));
  129. }
  130. @Handler
  131. public void handleUserErrorEvent(final UserErrorEvent userError) {
  132. addError(userError.getLevel(), userError.getMessage(), userError.getThrowable(),
  133. userError.getDetails(), false, isValidError(userError.getThrowable()));
  134. }
  135. /**
  136. * Adds a new error to the manager with the specified details. It is assumed that errors without
  137. * exceptions or details are not application errors.
  138. *
  139. * @param level The severity of the error
  140. * @param message The error message
  141. *
  142. * @since 0.6.3m1
  143. */
  144. protected void addError(final ErrorLevel level, final String message) {
  145. addError(level, message, (String) null, false);
  146. }
  147. /**
  148. * Adds a new error to the manager with the specified details.
  149. *
  150. * @param level The severity of the error
  151. * @param message The error message
  152. * @param exception The exception that caused this error
  153. * @param appError Whether or not this is an application error
  154. *
  155. * @since 0.6.3m1
  156. */
  157. protected void addError(final ErrorLevel level, final String message,
  158. final Throwable exception, final boolean appError) {
  159. addError(level, message, exception, null, appError, isValidError(exception));
  160. }
  161. /**
  162. * Adds a new error to the manager with the specified details.
  163. *
  164. * @param level The severity of the error
  165. * @param message The error message
  166. * @param details The details of the exception
  167. * @param appError Whether or not this is an application error
  168. *
  169. * @since 0.6.3m1
  170. */
  171. protected void addError(final ErrorLevel level, final String message, final String details,
  172. final boolean appError) {
  173. addError(level, message, null, details, appError, true);
  174. }
  175. /**
  176. * Adds a new error to the manager with the specified details.
  177. *
  178. * @param level The severity of the error
  179. * @param message The error message
  180. * @param exception The exception that caused the error, if any.
  181. * @param details The details of the exception, if any.
  182. * @param appError Whether or not this is an application error
  183. * @param canReport Whether or not this error can be reported
  184. *
  185. * @since 0.6.3m1
  186. */
  187. protected void addError(final ErrorLevel level, final String message,
  188. final Throwable exception, final String details, final boolean appError,
  189. final boolean canReport) {
  190. addError(getError(level, message, exception, details), appError, canReport);
  191. }
  192. protected void addError(
  193. final ProgramError error,
  194. final boolean appError,
  195. final boolean canReport) {
  196. final boolean dupe = addError(error);
  197. if (error.getLevel() == ErrorLevel.FATAL) {
  198. if (dupe) {
  199. error.setReportStatus(ErrorReportStatus.NOT_APPLICABLE);
  200. }
  201. } else if (!canReport || appError && !error.isValidSource() || !appError || dupe) {
  202. error.setReportStatus(ErrorReportStatus.NOT_APPLICABLE);
  203. } else if (sendReports) {
  204. sendError(error);
  205. }
  206. if (logReports) {
  207. error.save(errorsDirectory);
  208. }
  209. if (!dupe) {
  210. if (error.getLevel() == ErrorLevel.FATAL) {
  211. fireFatalError(error);
  212. } else {
  213. fireErrorAdded(error);
  214. }
  215. }
  216. }
  217. /**
  218. * Adds the specified error to the list of known errors and determines if it was previously
  219. * added.
  220. *
  221. * @param error The error to be added
  222. *
  223. * @return True if a duplicate error has already been registered, false otherwise
  224. */
  225. protected boolean addError(final ProgramError error) {
  226. final int index;
  227. synchronized (errors) {
  228. index = errors.indexOf(error);
  229. if (index == -1) {
  230. errors.add(error);
  231. } else {
  232. errors.get(index).updateLastDate();
  233. }
  234. }
  235. return index > -1;
  236. }
  237. /**
  238. * Retrieves a {@link ProgramError} that represents the specified details.
  239. *
  240. * @param level The severity of the error
  241. * @param message The error message
  242. * @param exception The exception that caused the error.
  243. * @param details The details of the exception
  244. *
  245. * @since 0.6.3m1
  246. * @return A corresponding ProgramError
  247. */
  248. protected ProgramError getError(final ErrorLevel level, final String message,
  249. final Throwable exception, final String details) {
  250. return new ProgramError(nextErrorID.getAndIncrement(), level, message, exception,
  251. details, new Date(), clientInfo);
  252. }
  253. /**
  254. * Determines whether or not the specified exception is one that we are willing to report.
  255. *
  256. * @param exception The exception to test
  257. *
  258. * @since 0.6.3m1
  259. * @return True if the exception may be reported, false otherwise
  260. */
  261. protected boolean isValidError(final Throwable exception) {
  262. Throwable target = exception;
  263. while (target != null) {
  264. for (Class<?> bad : BANNED_EXCEPTIONS) {
  265. if (bad.equals(target.getClass())) {
  266. return false;
  267. }
  268. }
  269. target = target.getCause();
  270. }
  271. return true;
  272. }
  273. /**
  274. * Sends an error to the developers.
  275. *
  276. * @param error error to be sent
  277. */
  278. public void sendError(final ProgramError error) {
  279. if (error.getReportStatus() != ErrorReportStatus.ERROR
  280. && error.getReportStatus() != ErrorReportStatus.WAITING) {
  281. return;
  282. }
  283. error.setReportStatus(ErrorReportStatus.QUEUED);
  284. reportQueue.add(error);
  285. if (reportThread == null || !reportThread.isAlive()) {
  286. reportThread = new ErrorReportingThread(reportQueue);
  287. reportThread.start();
  288. }
  289. }
  290. /**
  291. * Called when an error needs to be deleted from the list.
  292. *
  293. * @param error ProgramError that changed
  294. */
  295. public void deleteError(final ProgramError error) {
  296. synchronized (errors) {
  297. errors.remove(error);
  298. }
  299. fireErrorDeleted(error);
  300. }
  301. /**
  302. * Deletes all errors from the manager.
  303. *
  304. * @since 0.6.3m1
  305. */
  306. public void deleteAll() {
  307. synchronized (errors) {
  308. errors.forEach(this::fireErrorDeleted);
  309. errors.clear();
  310. }
  311. }
  312. /**
  313. * Returns the number of errors.
  314. *
  315. * @return Number of ProgramErrors
  316. */
  317. public int getErrorCount() {
  318. return errors.size();
  319. }
  320. /**
  321. * Returns the list of program errors.
  322. *
  323. * @return Program error list
  324. */
  325. public List<ProgramError> getErrors() {
  326. synchronized (errors) {
  327. return new LinkedList<>(errors);
  328. }
  329. }
  330. /**
  331. * Adds an ErrorListener to the listener list.
  332. *
  333. * @param listener Listener to add
  334. */
  335. public void addErrorListener(final ErrorListener listener) {
  336. if (listener == null) {
  337. return;
  338. }
  339. errorListeners.add(ErrorListener.class, listener);
  340. }
  341. /**
  342. * Removes an ErrorListener from the listener list.
  343. *
  344. * @param listener Listener to remove
  345. */
  346. public void removeErrorListener(final ErrorListener listener) {
  347. errorListeners.remove(ErrorListener.class, listener);
  348. }
  349. /**
  350. * Fired when the program encounters an error.
  351. *
  352. * @param error Error that occurred
  353. */
  354. protected void fireErrorAdded(final ProgramError error) {
  355. errorListeners.get(ErrorListener.class).stream().filter(ErrorListener::isReady)
  356. .forEach(listener -> {
  357. error.setHandled();
  358. listener.errorAdded(error);
  359. });
  360. if (!error.isHandled()) {
  361. System.err.println("An error has occurred: " + error.getLevel()
  362. + ": " + error.getMessage());
  363. for (String line : error.getTrace()) {
  364. System.err.println("\t" + line);
  365. }
  366. }
  367. }
  368. /**
  369. * Fired when the program encounters a fatal error.
  370. *
  371. * @param error Error that occurred
  372. */
  373. protected void fireFatalError(final ProgramError error) {
  374. final boolean restart;
  375. if (GraphicsEnvironment.isHeadless()) {
  376. System.err.println("A fatal error has occurred: " + error.getMessage());
  377. for (String line : error.getTrace()) {
  378. System.err.println("\t" + line);
  379. }
  380. restart = false;
  381. } else {
  382. final FatalErrorDialog fed = new FatalErrorDialog(error);
  383. fed.setVisible(true);
  384. try {
  385. synchronized (fed) {
  386. while (fed.isWaiting()) {
  387. fed.wait();
  388. }
  389. }
  390. } catch (InterruptedException ex) {
  391. //Oh well, carry on
  392. }
  393. restart = fed.getRestart();
  394. }
  395. try {
  396. synchronized (error) {
  397. while (!error.getReportStatus().isTerminal()) {
  398. error.wait();
  399. }
  400. }
  401. } catch (InterruptedException ex) {
  402. // Do nothing
  403. }
  404. if (restart) {
  405. System.exit(42);
  406. } else {
  407. System.exit(1);
  408. }
  409. }
  410. /**
  411. * Fired when an error is deleted.
  412. *
  413. * @param error Error that has been deleted
  414. */
  415. protected void fireErrorDeleted(final ProgramError error) {
  416. for (ErrorListener listener : errorListeners.get(ErrorListener.class)) {
  417. listener.errorDeleted(error);
  418. }
  419. }
  420. /**
  421. * Fired when an error's status is changed.
  422. *
  423. * @param error Error that has been altered
  424. */
  425. protected void fireErrorStatusChanged(final ProgramError error) {
  426. for (ErrorListener listener : errorListeners.get(ErrorListener.class)) {
  427. listener.errorStatusChanged(error);
  428. }
  429. }
  430. @Override
  431. public void configChanged(final String domain, final String key) {
  432. updateSettings();
  433. }
  434. /** Updates the settings used by this error manager. */
  435. protected void updateSettings() {
  436. try {
  437. sendReports = config.getOptionBool("general", "submitErrors")
  438. && !config.getOptionBool("temp", "noerrorreporting");
  439. logReports = config.getOptionBool("general", "logerrors");
  440. } catch (IllegalArgumentException ex) {
  441. sendReports = false;
  442. logReports = true;
  443. }
  444. }
  445. }