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.

SentryLoggingErrorManager.java 6.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  1. /*
  2. * Copyright (c) 2006-2015 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.config.ConfigBinder;
  24. import com.dmdirc.config.ConfigBinding;
  25. import com.dmdirc.events.ProgramErrorAddedEvent;
  26. import com.dmdirc.events.ProgramErrorEvent;
  27. import com.dmdirc.events.ProgramErrorStatusEvent;
  28. import com.dmdirc.interfaces.EventBus;
  29. import com.dmdirc.interfaces.config.AggregateConfigProvider;
  30. import com.google.common.base.Throwables;
  31. import java.util.Arrays;
  32. import java.util.Collection;
  33. import java.util.Optional;
  34. import java.util.concurrent.ExecutorService;
  35. import javax.inject.Inject;
  36. import javax.inject.Named;
  37. import javax.inject.Singleton;
  38. import net.engio.mbassy.listener.Handler;
  39. /**
  40. * Listens for {@link ProgramErrorEvent}s and reports these to Sentry.
  41. */
  42. @Singleton
  43. public class SentryLoggingErrorManager {
  44. /** A list of exceptions which we don't consider bugs and thus don't report. */
  45. private static final Class<?>[] BANNED_EXCEPTIONS = new Class<?>[]{
  46. NoSuchMethodError.class, NoClassDefFoundError.class,
  47. UnsatisfiedLinkError.class, AbstractMethodError.class,
  48. IllegalAccessError.class, OutOfMemoryError.class,
  49. NoSuchFieldError.class,};
  50. /** The event bus to listen for errors on. */
  51. private final EventBus eventBus;
  52. /** Sentry error reporter factory. */
  53. private final SentryErrorReporter sentryErrorReporter;
  54. /** Thread used for sending errors. */
  55. private final ExecutorService executorService;
  56. /** Whether to submit error reports. */
  57. private boolean submitReports;
  58. /** Temp no error reporting. */
  59. private boolean tempNoErrors;
  60. /** Whether or not to send error reports. */
  61. private boolean sendReports;
  62. @Inject
  63. public SentryLoggingErrorManager(final EventBus eventBus,
  64. final SentryErrorReporter sentryErrorReporter,
  65. @Named("errors") final ExecutorService executorService) {
  66. this.eventBus = eventBus;
  67. this.sentryErrorReporter = sentryErrorReporter;
  68. this.executorService = executorService;
  69. }
  70. /**
  71. * Initialises the error manager. Must be called before logging will start.
  72. */
  73. public void initialise(final AggregateConfigProvider config) {
  74. final ConfigBinder configBinder = config.getBinder();
  75. configBinder.bind(this, SentryLoggingErrorManager.class);
  76. eventBus.subscribe(this);
  77. }
  78. @Handler
  79. void handleErrorEvent(final ProgramErrorAddedEvent error) {
  80. final boolean appError = error.getError().isAppError();
  81. if (!isValidError(error.getError().getThrowable())
  82. || !isValidSource(error.getError().getThrowable())
  83. || !appError) {
  84. error.getError().setReportStatus(ErrorReportStatus.NOT_APPLICABLE);
  85. eventBus.publish(new ProgramErrorStatusEvent(error.getError()));
  86. } else if (sendReports) {
  87. sendError(error.getError());
  88. }
  89. }
  90. void sendError(final ProgramError error) {
  91. executorService.submit(new ErrorReportingRunnable(sentryErrorReporter, error));
  92. }
  93. @ConfigBinding(domain = "general", key = "submitErrors")
  94. void handleSubmitErrors(final boolean value) {
  95. submitReports = value;
  96. sendReports = submitReports && !tempNoErrors;
  97. }
  98. @ConfigBinding(domain = "temp", key="noerrorreporting")
  99. void handleNoErrorReporting(final boolean value) {
  100. tempNoErrors = value;
  101. sendReports = submitReports && !tempNoErrors;
  102. }
  103. /**
  104. * Determines whether or not the stack trace associated with this error is from a valid source.
  105. * A valid source is one that is within a DMDirc package (com.dmdirc), and is not the DMDirc
  106. * event queue.
  107. *
  108. * @return True if the source is valid, false otherwise
  109. */
  110. private boolean isValidSource(final Optional<Throwable> throwable) {
  111. if (throwable.isPresent()) {
  112. final String line = getSourceLine(Arrays.asList(
  113. Throwables.getStackTraceAsString(throwable.get()).split("\n")))
  114. .orElse("").trim();
  115. return line.startsWith("at com.dmdirc")
  116. && !line.startsWith("at com.dmdirc.addons.ui_swing.DMDircEventQueue");
  117. }
  118. return false;
  119. }
  120. /**
  121. * Returns the "source line" of this error, which is defined as the first line starting with a
  122. * DMDirc package name (com.dmdirc). If no such line is found, returns the first line of the
  123. * message.
  124. *
  125. * @return This error's source line
  126. */
  127. private Optional<String> getSourceLine(final Collection<String> trace) {
  128. for (String line : trace) {
  129. if (line.trim().startsWith("at com.dmdirc")) {
  130. return Optional.of(line);
  131. }
  132. }
  133. return trace.stream().findFirst();
  134. }
  135. /**
  136. * Determines whether or not the specified exception is one that we are willing to report.
  137. *
  138. * @param exception The exception to test
  139. *
  140. * @since 0.6.3m1
  141. * @return True if the exception may be reported, false otherwise
  142. */
  143. private boolean isValidError(final Optional<Throwable> exception) {
  144. if (exception.isPresent()) {
  145. @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
  146. Throwable target = exception.get();
  147. while (target != null) {
  148. for (Class<?> bad : BANNED_EXCEPTIONS) {
  149. if (bad.equals(target.getClass())) {
  150. return false;
  151. }
  152. }
  153. target = target.getCause();
  154. }
  155. return true;
  156. }
  157. return false;
  158. }
  159. }