def unhandled_error_observer(self, loop, context): """ This method is called when an unhandled error in Tribler is observed. It broadcasts the tribler_exception event. """ try: SentryReporter.ignore_logger(self._logger.name) exception = context.get('exception') ignored_message = None try: ignored_message = IGNORED_ERRORS.get( (exception.__class__, exception.errno), IGNORED_ERRORS.get(exception.__class__)) except (ValueError, AttributeError): pass if ignored_message is not None: self._logger.error(ignored_message if ignored_message != "" else context.get('message')) return text = str(exception or context.get('message')) # We already have a check for invalid infohash when adding a torrent, but if somehow we get this # error then we simply log and ignore it. if isinstance(exception, RuntimeError) and 'invalid info-hash' in text: self._logger.error("Invalid info-hash found") return text_long = text exc = context.get('exception') if exc: with StringIO() as buffer: print_exception(type(exc), exc, exc.__traceback__, file=buffer) text_long = text_long + "\n--LONG TEXT--\n" + buffer.getvalue() text_long = text_long + "\n--CONTEXT--\n" + str(context) self._logger.error("Unhandled exception occurred! %s", text_long, exc_info=None) sentry_event = SentryReporter.event_from_exception(exception) if not self.api_manager: return events = self.api_manager.get_endpoint('events') events.on_tribler_exception(text_long, sentry_event, self.config.get_core_error_reporting_requires_user_consent()) state = self.api_manager.get_endpoint('state') state.on_tribler_exception(text_long, sentry_event) except Exception as ex: SentryReporter.capture_exception(ex) raise ex
class CoreExceptionHandler: """ This class handles Python errors arising in the Core by catching them, adding necessary context, and sending them to the GUI through the events endpoint. It must be connected to the Asyncio loop. """ def __init__(self): self.logger = logging.getLogger("CoreExceptionHandler") self.report_callback: Optional[Callable[[ReportedError], None]] = None self.unreported_error: Optional[ReportedError] = None self.sentry_reporter = SentryReporter() @staticmethod def _get_long_text_from(exception: Exception): with StringIO() as buffer: print_exception(type(exception), exception, exception.__traceback__, file=buffer) return buffer.getvalue() @staticmethod def _is_ignored(exception: Exception): exception_class = exception.__class__ error_number = exception.errno if hasattr(exception, 'errno') else None if (exception_class, error_number) in IGNORED_ERRORS_BY_CODE: return True if exception_class not in IGNORED_ERRORS_BY_REGEX: return False pattern = IGNORED_ERRORS_BY_REGEX[exception_class] return re.search(pattern, str(exception)) is not None def _create_exception_from(self, message: str): text = f'Received error without exception: {message}' self.logger.warning(text) return Exception(text) def unhandled_error_observer(self, _, context): """ This method is called when an unhandled error in Tribler is observed. It broadcasts the tribler_exception event. """ try: self.sentry_reporter.ignore_logger(self.logger.name) context = context.copy() should_stop = context.pop('should_stop', True) message = context.pop('message', 'no message') exception = context.pop('exception', None) or self._create_exception_from(message) # Exception text = str(exception) if isinstance(exception, ComponentStartupException): should_stop = exception.component.tribler_should_stop_on_component_error exception = exception.__cause__ if self._is_ignored(exception): self.logger.warning(exception) return long_text = self._get_long_text_from(exception) self.logger.error(f"Unhandled exception occurred! {exception}\n{long_text}") reported_error = ReportedError( type=exception.__class__.__name__, text=text, long_text=long_text, context=str(context), event=self.sentry_reporter.event_from_exception(exception) or {}, should_stop=should_stop ) if self.report_callback: self.report_callback(reported_error) # pylint: disable=not-callable else: if not self.unreported_error: # We only remember the first unreported error, # as that was probably the root cause for # the crash self.unreported_error = reported_error except Exception as ex: self.sentry_reporter.capture_exception(ex) raise ex
def test_capture_exception(mocked_capture_exception: Mock, sentry_reporter: SentryReporter): # test that `capture_exception` passes an exception to `sentry_sdk` exception = Exception('test') sentry_reporter.capture_exception(exception) mocked_capture_exception.assert_called_with(exception)