Skip to content

Commit

Permalink
Cancel SentryReporter singleton
Browse files Browse the repository at this point in the history
  • Loading branch information
drew2a committed Dec 29, 2021
1 parent e57d7d3 commit 0473bd6
Show file tree
Hide file tree
Showing 24 changed files with 256 additions and 242 deletions.
29 changes: 14 additions & 15 deletions src/run_tribler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@
# A fix for "LookupError: unknown encoding: idna" error.
# Adding encodings.idna to hiddenimports is not enough.
# https://github.com/pyinstaller/pyinstaller/issues/1113
import encodings.idna # pylint: disable=unused-import
import logging.config
import os
import sys
from enum import Enum, auto

from PyQt5.QtCore import QSettings

Expand All @@ -15,11 +13,12 @@
from tribler_common.sentry_reporter.sentry_reporter import SentryReporter, SentryStrategy
from tribler_common.sentry_reporter.sentry_scrubber import SentryScrubber
from tribler_common.version_manager import VersionHistory

from tribler_core import start_core

from tribler_core.components.reporter.exception_handler import default_core_exception_handler

logger = logging.getLogger(__name__)


# pylint: disable=import-outside-toplevel, ungrouped-imports


Expand All @@ -38,20 +37,21 @@ def init_sentry_reporter():
as a URL for sending sentry's reports while a Tribler client running in
test mode
"""
sentry_reporter = default_core_exception_handler.sentry_reporter
from tribler_core.version import sentry_url, version_id
test_sentry_url = SentryReporter.get_test_sentry_url()
test_sentry_url = sentry_reporter.get_test_sentry_url()

if not test_sentry_url:
SentryReporter.init(sentry_url=sentry_url,
release_version=version_id,
scrubber=SentryScrubber(),
strategy=SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION)
sentry_reporter.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)
sentry_reporter.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')


Expand Down Expand Up @@ -99,7 +99,6 @@ def init_boot_logger():
process_checker.remove_lock_file()

else:
import tribler_gui
from tribler_gui.utilities import get_translator

logger.info('Running GUI' + ' in gui_test_mode' if parsed_args.gui_test_mode else '')
Expand Down Expand Up @@ -182,5 +181,5 @@ def init_boot_logger():
for handler in logging.getLogger().handlers:
handler.flush()

SentryReporter.global_strategy = SentryStrategy.SEND_SUPPRESSED
default_core_exception_handler.sentry_reporter.global_strategy = SentryStrategy.SEND_SUPPRESSED
raise
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Optional

from tribler_common.sentry_reporter.sentry_reporter import SentryReporter


Expand All @@ -6,7 +8,13 @@ class AddBreadcrumbOnShowMixin:
and send breadcrumbs on a show event.
"""

def __init__(self):
self.sentry_reporter: Optional[SentryReporter] = None

def set_sentry_reporter(self, sentry_reporter: SentryReporter):
self.sentry_reporter = sentry_reporter

def showEvent(self, *args):
super().showEvent(*args)

SentryReporter.add_breadcrumb(message=f'{self.__class__.__name__}.Show', category='UI', level='info')
self.sentry_reporter.add_breadcrumb(message=f'{self.__class__.__name__}.Show', category='UI', level='info')
132 changes: 62 additions & 70 deletions src/tribler-common/tribler_common/sentry_reporter/sentry_reporter.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
from contextvars import ContextVar
from enum import Enum, auto
from hashlib import md5

from faker import Faker
from typing import Dict, List, Optional

import sentry_sdk
from faker import Faker
from sentry_sdk.integrations.logging import LoggingIntegration, ignore_logger
from sentry_sdk.integrations.threading import ThreadingIntegration

Expand Down Expand Up @@ -54,33 +54,34 @@ class SentryStrategy(Enum):


@contextmanager
def this_sentry_strategy(strategy: SentryStrategy):
saved_strategy = SentryReporter.thread_strategy.get()
def this_sentry_strategy(reporter, strategy: SentryStrategy):
saved_strategy = reporter.thread_strategy.get()
try:
SentryReporter.thread_strategy.set(strategy)
yield
reporter.thread_strategy.set(strategy)
yield reporter
finally:
SentryReporter.thread_strategy.set(saved_strategy)
reporter.thread_strategy.set(saved_strategy)


class SentryReporter:
"""SentryReporter designed for sending reports to the Sentry server from
a Tribler Client.
"""

scrubber = None
last_event = None
ignored_exceptions = [KeyboardInterrupt, SystemExit]
# more info about how SentryReporter choose a strategy see in
# SentryReporter.get_actual_strategy()
global_strategy = SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION
thread_strategy = ContextVar('context_strategy', default=None)
def __init__(self):
self.scrubber = None
self.last_event = None
self.ignored_exceptions = [KeyboardInterrupt, SystemExit]
# more info about how SentryReporter choose a strategy see in
# SentryReporter.get_actual_strategy()
self.global_strategy = SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION
self.thread_strategy = ContextVar('context_strategy', default=None)

_sentry_logger_name = 'SentryReporter'
_logger = logging.getLogger(_sentry_logger_name)
self._sentry_logger_name = 'SentryReporter'
self._logger = logging.getLogger(self._sentry_logger_name)

@staticmethod
def init(sentry_url='', release_version='', scrubber=None, strategy=SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION):
def init(self, sentry_url='', release_version='', scrubber=None,
strategy=SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION):
"""Initialization.
This method should be called in each process that uses SentryReporter.
Expand All @@ -102,9 +103,9 @@ def scrub_event(self, event):
Returns:
Sentry Guard.
"""
SentryReporter._logger.debug(f"Init: {sentry_url}")
SentryReporter.scrubber = scrubber
SentryReporter.global_strategy = strategy
self._logger.debug(f"Init: {sentry_url}")
self.scrubber = scrubber
self.global_strategy = strategy

rv = sentry_sdk.init(
sentry_url,
Expand All @@ -117,21 +118,19 @@ def scrub_event(self, event):
),
ThreadingIntegration(propagate_hub=True),
],
before_send=SentryReporter._before_send,
before_send=self._before_send,
ignore_errors=[KeyboardInterrupt],
)

ignore_logger(SentryReporter._sentry_logger_name)
ignore_logger(self._sentry_logger_name)

return rv

@staticmethod
def ignore_logger(logger_name):
SentryReporter._logger.debug(f"Ignore logger: {logger_name}")
def ignore_logger(self, logger_name: str):
self._logger.debug(f"Ignore logger: {logger_name}")
ignore_logger(logger_name)

@staticmethod
def add_breadcrumb(message='', category='', level='info', **kwargs):
def add_breadcrumb(self, message='', category='', level='info', **kwargs):
"""Adds a breadcrumb for current Sentry client.
It is necessary to specify a message, a category and a level to make this
Expand All @@ -142,13 +141,12 @@ def add_breadcrumb(message='', category='', level='info', **kwargs):
"""
crumb = {'message': message, 'category': category, 'level': level}

SentryReporter._logger.debug(f"Add the breadcrumb: {crumb}")
self._logger.debug(f"Add the breadcrumb: {crumb}")

return sentry_sdk.add_breadcrumb(crumb, **kwargs)

@staticmethod
def send_event(event=None, post_data=None, sys_info=None, additional_tags=None,
retrieve_error_message_from_stacktrace=False):
def send_event(self, event: Dict = None, post_data: Dict = None, sys_info: Dict = None,
additional_tags: List[str] = None, retrieve_error_message_from_stacktrace=False):
"""Send the event to the Sentry server
This method
Expand All @@ -172,7 +170,7 @@ def send_event(event=None, post_data=None, sys_info=None, additional_tags=None,
Returns:
Event that was sent to Sentry server
"""
SentryReporter._logger.info(f"Send: {post_data}, {event}")
self._logger.info(f"Send: {post_data}, {event}")

if event is None:
return event
Expand Down Expand Up @@ -229,13 +227,12 @@ def send_event(event=None, post_data=None, sys_info=None, additional_tags=None,
'value': exception_value[1]
})

with this_sentry_strategy(SentryStrategy.SEND_ALLOWED):
with this_sentry_strategy(self, SentryStrategy.SEND_ALLOWED):
sentry_sdk.capture_event(event)

return event

@staticmethod
def get_confirmation(exception):
def get_confirmation(self, exception):
"""Get confirmation on sending exception to the Team.
There are two message boxes, that will be triggered:
Expand All @@ -251,10 +248,10 @@ def get_confirmation(exception):
try:
from PyQt5.QtWidgets import QApplication, QMessageBox
except ImportError:
SentryReporter._logger.debug("PyQt5 is not available. User confirmation is not possible.")
self._logger.debug("PyQt5 is not available. User confirmation is not possible.")
return False

SentryReporter._logger.debug(f"Get confirmation: {exception}")
self._logger.debug(f"Get confirmation: {exception}")

_ = QApplication(sys.argv)
messagebox = QMessageBox(icon=QMessageBox.Critical, text=f'{exception}.')
Expand All @@ -271,31 +268,28 @@ def get_confirmation(exception):

return messagebox.exec() == QMessageBox.Yes

@staticmethod
def capture_exception(exception):
SentryReporter._logger.info(f"Capture exception: {exception}")
def capture_exception(self, exception):
self._logger.info(f"Capture exception: {exception}")
sentry_sdk.capture_exception(exception)

@staticmethod
def event_from_exception(exception):
def event_from_exception(self, exception) -> Dict:
"""This function format the exception by passing it through sentry
Args:
exception: an exception that will be passed to `sentry_sdk.capture_exception(exception)`
Returns:
the event that has been saved in `_before_send` method
"""
SentryReporter._logger.info(f"Event from exception: {exception}")
self._logger.info(f"Event from exception: {exception}")

if not exception:
return exception
return {}

with this_sentry_strategy(SentryStrategy.SEND_SUPPRESSED):
with this_sentry_strategy(self, SentryStrategy.SEND_SUPPRESSED):
sentry_sdk.capture_exception(exception)
return SentryReporter.last_event
return self.last_event

@staticmethod
def set_user(user_id):
def set_user(self, user_id):
"""Set the user to identify the event on a Sentry server
The algorithm is the following:
Expand All @@ -313,7 +307,7 @@ def set_user(user_id):
# calculate hash to keep real `user_id` in secret
user_id_hash = md5(user_id).hexdigest()

SentryReporter._logger.debug(f"Set user: {user_id_hash}")
self._logger.debug(f"Set user: {user_id_hash}")

Faker.seed(user_id_hash)
user_name = Faker().name()
Expand All @@ -322,17 +316,16 @@ def set_user(user_id):
sentry_sdk.set_user(user)
return user

@staticmethod
def get_actual_strategy():
def get_actual_strategy(self):
"""This method is used to determine actual strategy.
Strategy can be global: SentryReporter.strategy
and local: SentryReporter._context_strategy.
Strategy can be global: self.strategy
and local: self._context_strategy.
Returns: the local strategy if it is defined, the global strategy otherwise
"""
strategy = SentryReporter.thread_strategy.get()
return strategy if strategy else SentryReporter.global_strategy
strategy = self.thread_strategy.get()
return strategy if strategy else self.global_strategy

@staticmethod
def get_test_sentry_url():
Expand All @@ -342,15 +335,14 @@ def get_test_sentry_url():
def is_in_test_mode():
return bool(SentryReporter.get_test_sentry_url())

@staticmethod
def _before_send(event, hint):
def _before_send(self, event: Dict, hint: Dict) -> Optional[Dict]:
"""The method that is called before each send. Both allowed and
disallowed.
The algorithm:
1. If sending is allowed, then scrub the event and send.
2. If sending is disallowed, then store the event in
`SentryReporter.last_event`
`self.last_event`
Args:
event: event that generated by Sentry
Expand All @@ -363,30 +355,30 @@ def _before_send(event, hint):
return event

# trying to get context-depending strategy first
strategy = SentryReporter.get_actual_strategy()
strategy = self.get_actual_strategy()

SentryReporter._logger.info(f"Before send strategy: {strategy}")
self._logger.info(f"Before send strategy: {strategy}")

exc_info = get_value(hint, 'exc_info')
error_type = get_first_item(exc_info)

if error_type in SentryReporter.ignored_exceptions:
SentryReporter._logger.debug(f"Exception is in ignored: {hint}. Skipped.")
if error_type in self.ignored_exceptions:
self._logger.debug(f"Exception is in ignored: {hint}. Skipped.")
return None

if strategy == SentryStrategy.SEND_SUPPRESSED:
SentryReporter._logger.debug("Suppress sending. Storing the event.")
SentryReporter.last_event = event
self._logger.debug("Suppress sending. Storing the event.")
self.last_event = event
return None

if strategy == SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION:
SentryReporter._logger.debug("Request confirmation.")
if not SentryReporter.get_confirmation(hint):
self._logger.debug("Request confirmation.")
if not self.get_confirmation(hint):
return None

# clean up the event
SentryReporter._logger.debug(f"Clean up the event with scrubber: {SentryReporter.scrubber}")
if SentryReporter.scrubber:
event = SentryReporter.scrubber.scrub_event(event)
self._logger.debug(f"Clean up the event with scrubber: {self.scrubber}")
if self.scrubber:
event = self.scrubber.scrub_event(event)

return event
Loading

0 comments on commit 0473bd6

Please sign in to comment.