Beispiel #1
0
def test_add_breadcrumb():
    # test: None does not produce error
    assert SentryReporter.add_breadcrumb(None, None, None) is None
    assert SentryReporter.add_breadcrumb('message', 'category',
                                         'level') is None
    assert SentryReporter.add_breadcrumb(
        'message', 'category', 'level', named_arg='some') is None
Beispiel #2
0
    def showEvent(self, *args):
        super().showEvent(*args)

        SentryReporter.add_breadcrumb(
            message=f'{self.__class__.__name__}.Show',
            category='UI',
            level='info')
Beispiel #3
0
def test_send_defaults():
    assert SentryReporter.send_event(None, None, None) is None

    assert SentryReporter.send_event(event={}) == {
        'contexts': {
            'browser': {
                'name': 'Tribler',
                'version': None
            },
            'reporter': {
                '_stacktrace': [],
                '_stacktrace_context': [],
                '_stacktrace_extra': [],
                'comments': None,
                OS_ENVIRON: {},
                'sysinfo': {},
                'events': {},
            },
        },
        'tags': {
            'machine': None,
            'os': None,
            'platform': None,
            PLATFORM_DETAILS: None,
            'version': None
        },
    }
Beispiel #4
0
    def on_send_clicked(self, checked):
        self.send_report_button.setEnabled(False)
        self.send_report_button.setText(tr("SENDING..."))

        endpoint = 'http://reporter.tribler.org/report'

        sys_info = ""
        sys_info_dict = defaultdict(lambda: [])
        for ind in range(self.env_variables_list.topLevelItemCount()):
            item = self.env_variables_list.topLevelItem(ind)
            key = item.text(0)
            value = item.text(1)

            sys_info += f"{key}\t{value}\n"
            sys_info_dict[key].append(value)

        comments = self.comments_text_edit.toPlainText()
        if len(comments) == 0:
            comments = tr("Not provided")
        stack = self.error_text_edit.toPlainText()

        post_data = {
            "version": self.tribler_version,
            "machine": platform.machine(),
            "os": platform.platform(),
            "timestamp": int(time.time()),
            "sysinfo": sys_info,
            "comments": comments,
            "stack": stack,
        }

        SentryReporter.send_event(self.sentry_event, post_data, sys_info_dict, self.additional_tags)

        TriblerNetworkRequest(endpoint, self.on_report_sent, raw_data=tribler_urlencode(post_data), method='POST')
def test_before_send_scrubber_exists(sentry_reporter: SentryReporter):
    # test that in case of a set scrubber, it will be called for scrubbing an event
    event = {'some': 'event'}

    sentry_reporter.global_strategy = SentryStrategy.SEND_ALLOWED
    sentry_reporter.scrubber = Mock()
    assert sentry_reporter._before_send(event, None)
    sentry_reporter.scrubber.scrub_event.assert_called_with(event)
Beispiel #6
0
    def on_tab_button_click(self, clicked_button):
        SentryReporter.add_breadcrumb(
            message=f'{clicked_button.objectName()}.Click',
            category='UI',
            level='info')

        self.deselect_all_buttons(except_select=clicked_button)
        self.clicked_tab_button.emit(clicked_button.objectName())
Beispiel #7
0
    def tribler_started_event(self, event_dict):
        # if public key format will be changed, don't forget to change it
        # at the core side as well
        public_key = event_dict["public_key"]
        if public_key:
            SentryReporter.set_user(public_key.encode('utf-8'))

        self.tribler_started.emit(event_dict["version"])
Beispiel #8
0
    def __init__(self, tribler_window):
        logger_name = self.__class__.__name__
        self._logger = logging.getLogger(logger_name)
        SentryReporter.ignore_logger(logger_name)

        self.tribler_window = tribler_window

        self._handled_exceptions = set()
        self._tribler_stopped = False
def test_init(mocked_init: Mock, sentry_reporter: SentryReporter):
    # test that `init` method set all necessary variables and calls `sentry_sdk.init()`
    sentry_reporter.init(sentry_url='url',
                         release_version='release',
                         scrubber=SentryScrubber(),
                         strategy=SentryStrategy.SEND_SUPPRESSED)
    assert sentry_reporter.scrubber
    assert sentry_reporter.global_strategy == SentryStrategy.SEND_SUPPRESSED
    mocked_init.assert_called_once()
Beispiel #10
0
def test_get_actual_strategy():
    SentryReporter.thread_strategy.set(None)  # default
    SentryReporter.global_strategy = SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION

    assert SentryReporter.get_actual_strategy(
    ) == SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION

    SentryReporter.thread_strategy.set(SentryStrategy.SEND_ALLOWED)
    assert SentryReporter.get_actual_strategy() == SentryStrategy.SEND_ALLOWED
Beispiel #11
0
def test_set_user():
    assert SentryReporter.set_user(b'some_id') == {
        'id': 'db69fe66ec6b6b013c2f7d271ce17cae',
        'username': '******',
    }

    assert SentryReporter.set_user(b'11111100100') == {
        'id': '91f900f528d5580581197c2c6a4adbbc',
        'username': '******',
    }
Beispiel #12
0
def test_get_confirmation(sentry_reporter: SentryReporter):
    # test that `get_confirmation` calls `QApplication` and `QMessageBox` from `PyQt5.QtWidgets`
    mocked_QApplication = Mock()
    mocked_QMessageBox = MagicMock()

    with patch_import('PyQt5.QtWidgets',
                      strict=True,
                      QApplication=mocked_QApplication,
                      QMessageBox=mocked_QMessageBox):
        sentry_reporter.get_confirmation(Exception('test'))
        mocked_QApplication.assert_called()
        mocked_QMessageBox.assert_called()
Beispiel #13
0
    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
Beispiel #14
0
    def gui_error(self, *exc_info):
        if self._tribler_stopped:
            return

        info_type, info_error, tb = exc_info
        if info_type in self._handled_exceptions:
            return
        self._handled_exceptions.add(info_type)

        text = "".join(traceback.format_exception(info_type, info_error, tb))

        if info_type is CoreConnectTimeoutError:
            text = text + self.tribler_window.core_manager.core_traceback
            self._stop_tribler(text)

        self._logger.error(text)

        FeedbackDialog(
            parent=self.tribler_window,
            exception_text=text,
            tribler_version=self.tribler_window.tribler_version,
            start_time=self.tribler_window.start_time,
            sentry_event=SentryReporter.event_from_exception(info_error),
            error_reporting_requires_user_consent=True,
            stop_application_on_close=self._tribler_stopped,
            additional_tags={
                'source': 'gui'
            }).show()
Beispiel #15
0
    def on_error(self, error, reschedule_on_err):
        self._logger.info(f"Got Tribler core error: {error}")

        SentryReporter.ignore_logger(self._logger.name)
        if self.remaining_connection_attempts <= 0:
            raise CoreConnectTimeoutError(
                "Could not connect with the Tribler Core within 60 seconds")

        self.remaining_connection_attempts -= 1

        if reschedule_on_err:
            # Reschedule an attempt
            self.connect_timer = QTimer()
            self.connect_timer.setSingleShot(True)
            connect(self.connect_timer.timeout, self.connect)
            self.connect_timer.start(500)
Beispiel #16
0
def test_send_additional_tags():
    assert SentryReporter.send_event(
        event={}, additional_tags={'tag_key': 'tag_value'}) == {
            'contexts': {
                'browser': {
                    'name': 'Tribler',
                    'version': None
                },
                'reporter': {
                    '_stacktrace': [],
                    '_stacktrace_context': [],
                    '_stacktrace_extra': [],
                    'comments': None,
                    OS_ENVIRON: {},
                    'sysinfo': {},
                    'events': {},
                },
            },
            'tags': {
                'machine': None,
                'os': None,
                'platform': None,
                'platform.details': None,
                'version': None,
                'tag_key': 'tag_value',
            },
        }
Beispiel #17
0
def run_tribler_core(api_port, api_key, state_dir, gui_test_mode=False):
    """
    This method will start a new Tribler session.
    Note that there is no direct communication between the GUI process and the core: all communication is performed
    through the HTTP API.
    """
    logger.info(f'Start tribler core. API port: "{api_port}". '
                f'API key: "{api_key}". State dir: "{state_dir}". '
                f'Core test mode: "{gui_test_mode}"')

    config = TriblerConfig.load(
        file=state_dir / CONFIG_FILE_NAME,
        state_dir=state_dir,
        reset_config_on_error=True)
    config.gui_test_mode = gui_test_mode

    if SentryReporter.is_in_test_mode():
        default_core_exception_handler.sentry_reporter.global_strategy = SentryStrategy.SEND_ALLOWED

    config.api.http_port = int(api_port)
    # If the API key is set to an empty string, it will remain disabled
    if config.api.key not in ('', api_key):
        config.api.key = api_key
        config.write()  # Immediately write the API key so other applications can use it
    config.api.http_enabled = True

    priority_order = config.resource_monitor.cpu_priority
    set_process_priority(pid=os.getpid(), priority_order=priority_order)

    # Enable tracer if --trace-debug or --trace-exceptions flag is present in sys.argv
    log_dir = config.general.get_path_as_absolute('log_dir', config.state_dir)
    trace_logger = check_and_enable_code_tracing('core', log_dir)

    logging.getLogger('asyncio').setLevel(logging.WARNING)

    if sys.platform.startswith('win'):
        # TODO for the moment being, we use the SelectorEventLoop on Windows, since with the ProactorEventLoop, ipv8
        # peer discovery becomes unstable. Also see issue #5485.
        asyncio.set_event_loop(asyncio.SelectorEventLoop())

    loop = asyncio.get_event_loop()
    exception_handler = default_core_exception_handler
    loop.set_exception_handler(exception_handler.unhandled_error_observer)

    try:
        loop.run_until_complete(core_session(config, components=list(components_gen(config))))
    finally:
        if trace_logger:
            trace_logger.close()

        # Flush the logs to the file before exiting
        for handler in logging.getLogger().handlers:
            handler.flush()
Beispiel #18
0
def test_feedback_dialog(window):
    def screenshot_dialog():
        screenshot(dialog, name="feedback_dialog")
        dialog.close()

    reported_error = ReportedError('type', 'text', {})
    sentry_reporter = SentryReporter()
    dialog = FeedbackDialog(window, sentry_reporter, reported_error, "1.2.3",
                            23)
    dialog.closeEvent = lambda _: None  # Otherwise, the application will stop
    QTimer.singleShot(1000, screenshot_dialog)
    dialog.exec_()
Beispiel #19
0
def test_event_from_exception(mocked_capture_exception: Mock,
                              sentry_reporter: SentryReporter):
    # test that `event_from_exception` returns '{}' in case of an empty exception
    assert sentry_reporter.event_from_exception(None) == {}

    # test that `event_from_exception` calls `capture_exception` from `sentry_sdk`
    exception = Exception('test')
    sentry_reporter.thread_strategy = Mock()

    def capture_exception(_):
        # this behaviour normally is way more complicated, but at the end, `capture_exception` should transform
        # the exception to a sentry event and this event should be stored in `sentry_reporter.last_event`
        sentry_reporter.last_event = {'sentry': 'event'}

    mocked_capture_exception.side_effect = capture_exception

    sentry_reporter.event_from_exception(exception)

    mocked_capture_exception.assert_called_with(exception)
    sentry_reporter.thread_strategy.set.assert_any_call(
        SentryStrategy.SEND_SUPPRESSED)
    assert sentry_reporter.last_event == {'sentry': 'event'}
Beispiel #20
0
def init_sentry_reporter():
    """ Initialise sentry reporter

    We use `sentry_url` as a URL for normal tribler mode and TEST_SENTRY_URL
    as a URL for sending sentry's reports while a Tribler client running in
    test mode
    """
    test_sentry_url = os.environ.get('TEST_SENTRY_URL', None)

    if not test_sentry_url:
        SentryReporter.init(
            sentry_url=sentry_url,
            release_version=version_id,
            scrubber=SentryScrubber(),
            strategy=SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION)
        logger.info('Sentry has been initialised in normal mode')
    else:
        SentryReporter.init(sentry_url=test_sentry_url,
                            release_version=version_id,
                            scrubber=None,
                            strategy=SentryStrategy.SEND_ALLOWED)
        logger.info('Sentry has been initialised in debug mode')
Beispiel #21
0
def test_send_sys_info():
    assert SentryReporter.send_event(
        event={},
        sys_info={
            'platform': ['darwin'],
            PLATFORM_DETAILS: ['details'],
            OS_ENVIRON: ['KEY:VALUE', 'KEY1:VALUE1'],
            'event_1': [{
                'type': ''
            }],
            'request_1': [{}],
            'event_2': [],
            'request_2': [],
        },
    ) == {
        'contexts': {
            'browser': {
                'name': 'Tribler',
                'version': None
            },
            'reporter': {
                '_stacktrace': [],
                '_stacktrace_context': [],
                '_stacktrace_extra': [],
                'comments': None,
                OS_ENVIRON: {
                    'KEY': 'VALUE',
                    'KEY1': 'VALUE1'
                },
                'sysinfo': {
                    'platform': ['darwin'],
                    PLATFORM_DETAILS: ['details']
                },
                'events': {
                    'event_1': [{
                        'type': ''
                    }],
                    'request_1': [{}],
                    'event_2': [],
                    'request_2': []
                },
            },
        },
        'tags': {
            'machine': None,
            'os': None,
            'platform': 'darwin',
            'platform.details': 'details',
            'version': None
        },
    }
Beispiel #22
0
def test_add_breadcrumb(mocked_add_breadcrumb: Mock,
                        sentry_reporter: SentryReporter):
    # test that `add_breadcrumb` passes all necessary arguments to `sentry_sdk`
    assert sentry_reporter.add_breadcrumb('message',
                                          'category',
                                          'level',
                                          named_arg='some')
    mocked_add_breadcrumb.assert_called_with(
        {
            'message': 'message',
            'category': 'category',
            'level': 'level'
        },
        named_arg='some')
Beispiel #23
0
def test_feedback_dialog_report_sent(window):
    def screenshot_dialog():
        screenshot(dialog, name="feedback_dialog")
        dialog.close()

    def on_report_sent():
        on_report_sent.did_send_report = True

    on_report_sent.did_send_report = False
    reported_error = ReportedError(
        '', 'Tribler GUI Test to test sending crash report works', {})
    sentry_reporter = SentryReporter()
    dialog = FeedbackDialog(window, sentry_reporter, reported_error, "1.2.3",
                            23)
    dialog.closeEvent = lambda _: None  # Otherwise, the application will stop
    dialog.on_report_sent = on_report_sent
    QTest.mouseClick(dialog.send_report_button, Qt.LeftButton)
    QTimer.singleShot(1000, screenshot_dialog)
    dialog.exec_()
    assert on_report_sent.did_send_report
Beispiel #24
0
def test_send_post_data():
    assert SentryReporter.send_event(
        event={'a': 'b'},
        post_data={
            "version": '0.0.0',
            "machine": 'x86_64',
            "os": 'posix',
            "timestamp": 42,
            "sysinfo": '',
            "comments": 'comment',
            "stack": 'l1\nl2--LONG TEXT--l3\nl4',
        },
    ) == {
        'a': 'b',
        'contexts': {
            'browser': {
                'name': 'Tribler',
                'version': '0.0.0'
            },
            'reporter': {
                '_stacktrace': ['l1', 'l2'],
                '_stacktrace_context': [],
                '_stacktrace_extra': ['l3', 'l4'],
                'comments': 'comment',
                'os.environ': {},
                'sysinfo': {},
                'events': {},
            },
        },
        'tags': {
            'machine': 'x86_64',
            'os': 'posix',
            'platform': None,
            PLATFORM_DETAILS: None,
            'version': '0.0.0'
        },
    }
Beispiel #25
0
"""
This package contains the code for the GUI, written in pyQt.
"""
from tribler_common.sentry_reporter.sentry_reporter import SentryReporter

gui_sentry_reporter = SentryReporter()
Beispiel #26
0
 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()
Beispiel #27
0
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
Beispiel #28
0
def test_get_confirmation_no_qt(sentry_reporter: SentryReporter):
    assert not sentry_reporter.get_confirmation(Exception('test'))
Beispiel #29
0
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)
Beispiel #30
0
    def __init__(  # pylint: disable=too-many-arguments, too-many-locals
        self,
        parent,
        sentry_reporter: SentryReporter,
        reported_error: ReportedError,
        tribler_version,
        start_time,
        stop_application_on_close=True,
        additional_tags=None,
        retrieve_error_message_from_stacktrace=False,
    ):
        QDialog.__init__(self, parent)

        uic.loadUi(get_ui_file_path('feedback_dialog.ui'), self)

        self.setWindowTitle(tr("Unexpected error"))
        self.selected_item_index = 0
        self.tribler_version = tribler_version
        self.reported_error = reported_error
        self.scrubber = SentryScrubber()
        self.sentry_reporter = sentry_reporter
        self.stop_application_on_close = stop_application_on_close
        self.additional_tags = additional_tags
        self.retrieve_error_message_from_stacktrace = retrieve_error_message_from_stacktrace

        # Qt 5.2 does not have the setPlaceholderText property
        if hasattr(self.comments_text_edit, "setPlaceholderText"):
            placeholder = tr(
                "What were you doing before this crash happened? "
                "This information will help Tribler developers to figure out and fix the issue quickly."
            )
            self.comments_text_edit.setPlaceholderText(placeholder)

        def add_item_to_info_widget(key, value):
            item = QTreeWidgetItem(self.env_variables_list)
            item.setText(0, key)
            scrubbed_value = self.scrubber.scrub_text(value)
            item.setText(1, scrubbed_value)

        text_for_viewing = '\n'.join(
            (
                reported_error.text,
                LONG_TEXT_DELIMITER,
                reported_error.long_text,
                CONTEXT_DELIMITER,
                reported_error.context,
            )
        )
        stacktrace = self.scrubber.scrub_text(text_for_viewing.rstrip())
        self.error_text_edit.setPlainText(stacktrace)
        connect(self.cancel_button.clicked, self.on_cancel_clicked)
        connect(self.send_report_button.clicked, self.on_send_clicked)

        # Add machine information to the tree widget
        add_item_to_info_widget('os.getcwd', f'{os.getcwd()}')
        add_item_to_info_widget('sys.executable', f'{sys.executable}')

        add_item_to_info_widget('os', os.name)
        add_item_to_info_widget('platform', sys.platform)
        add_item_to_info_widget('platform.details', platform.platform())
        add_item_to_info_widget('platform.machine', platform.machine())
        add_item_to_info_widget('python.version', sys.version)
        add_item_to_info_widget('indebug', str(__debug__))
        add_item_to_info_widget('tribler_uptime', f"{time.time() - start_time}")

        for argv in sys.argv:
            add_item_to_info_widget('sys.argv', f'{argv}')

        for path in sys.path:
            add_item_to_info_widget('sys.path', f'{path}')

        for key in os.environ.keys():
            add_item_to_info_widget('os.environ', f'{key}: {os.environ[key]}')

        # Add recent requests to feedback dialog
        request_ind = 1
        for request, status_code in sorted(tribler_performed_requests, key=lambda rq: rq[0].time)[-30:]:
            add_item_to_info_widget(
                'request_%d' % request_ind,
                '%s %s %s (time: %s, code: %s)'
                % (request.url, request.method, request.raw_data, request.time, status_code),
            )
            request_ind += 1

        # Add recent events to feedback dialog
        events_ind = 1
        for event, event_time in received_events[:30][::-1]:
            add_item_to_info_widget('event_%d' % events_ind, f'{json.dumps(event)} (time: {event_time})')
            events_ind += 1

        # Users can remove specific lines in the report
        connect(self.env_variables_list.customContextMenuRequested, self.on_right_click_item)

        self.send_automatically = SentryReporter.is_in_test_mode()
        if self.send_automatically:
            self.stop_application_on_close = True
            self.on_send_clicked(True)