Skip to content

Commit

Permalink
Merge pull request #5870 from drew2a/sentry/autosend_settings
Browse files Browse the repository at this point in the history
Add an option to automatically send exceptions
  • Loading branch information
drew2a committed Dec 21, 2020
2 parents 87fed8c + cf01b65 commit 73a8f73
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 106 deletions.
8 changes: 6 additions & 2 deletions src/run_tribler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import sys
from asyncio import ensure_future, get_event_loop

from tribler_common.sentry_reporter.sentry_reporter import SentryReporter
from tribler_common.sentry_reporter.sentry_reporter import SentryReporter, SentryStrategy
from tribler_common.sentry_reporter.sentry_scrubber import SentryScrubber

import tribler_core
Expand Down Expand Up @@ -64,6 +64,10 @@ async def start_tribler():
state_dir = get_versioned_state_directory(root_state_dir)

config = TriblerConfig(state_dir, config_file=state_dir / CONFIG_FILENAME)

if not config.get_error_reporting_requires_user_consent():
SentryReporter.global_strategy = SentryStrategy.SEND_ALLOWED

config.set_api_http_port(int(api_port))
# If the API key is set to an empty string, it will remain disabled
if config.get_api_key() not in ('', api_key):
Expand All @@ -90,7 +94,7 @@ async def start_tribler():

if __name__ == "__main__":
SentryReporter.init(sentry_url=sentry_url, release_version=version_id, scrubber=SentryScrubber(),
strategy=SentryReporter.Strategy.SEND_ALLOWED_WITH_CONFIRMATION)
strategy=SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION)
# Get root state directory (e.g. from environment variable or from system default)
root_state_dir = get_root_state_directory()
# Check whether we need to start the core or the user interface
Expand Down
138 changes: 77 additions & 61 deletions src/tribler-common/tribler_common/sentry_reporter/sentry_reporter.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import sys
from contextlib import contextmanager
from contextvars import ContextVar
from enum import Enum, auto
from hashlib import md5
Expand Down Expand Up @@ -33,39 +34,48 @@
RELEASE = 'release'


class SentryReporter:
"""SentryReporter designed for sending reports to the Sentry server from
a Tribler Client.
class SentryStrategy(Enum):
"""Class describes all available Sentry Strategies
It can work with 3 strategies:
SentryReporter can work with 3 strategies:
1. Send reports are allowed
2. Send reports are allowed with a confirmation dialog
3. Send reports are suppressed (disallowed, but the last event will be stored)
Example of how to change a strategy:
```
SentryReporter.strategy.set(SentryReporter.Strategy.SEND_SUPPRESSED)
```
SentryReporter is thread-safe.
3. Send reports are suppressed (but the last event will be stored)
"""

class Strategy(Enum):
SEND_ALLOWED = auto()
SEND_ALLOWED_WITH_CONFIRMATION = auto()
SEND_SUPPRESSED = auto() # the last event will be stored in `SentryReporter.last_event`
SEND_ALLOWED = auto()
SEND_ALLOWED_WITH_CONFIRMATION = auto()
SEND_SUPPRESSED = auto()

last_event = None

ignored_exceptions = [KeyboardInterrupt, SystemExit]
@contextmanager
def this_sentry_strategy(strategy: SentryStrategy):
saved_strategy = SentryReporter.thread_strategy.get()
try:
SentryReporter.thread_strategy.set(strategy)
yield
finally:
SentryReporter.thread_strategy.set(saved_strategy)


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

strategy = ContextVar('Sentry', default=Strategy.SEND_ALLOWED_WITH_CONFIRMATION)
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)

_scrubber = None
_sentry_logger_name = 'SentryReporter'
_logger = logging.getLogger(_sentry_logger_name)

@staticmethod
def init(sentry_url='', release_version='', scrubber=None, strategy=Strategy.SEND_ALLOWED_WITH_CONFIRMATION):
def init(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 @@ -88,8 +98,8 @@ def scrub_event(self, event):
Sentry Guard.
"""
SentryReporter._logger.debug(f"Init: {sentry_url}")
SentryReporter._scrubber = scrubber
SentryReporter.strategy.set(strategy)
SentryReporter.scrubber = scrubber
SentryReporter.global_strategy = strategy

rv = sentry_sdk.init(
sentry_url,
Expand Down Expand Up @@ -159,44 +169,40 @@ def send_event(event, post_data=None, sys_info=None):
if event is None:
return event

saved_strategy = SentryReporter.strategy.get()
try:
SentryReporter.strategy.set(SentryReporter.Strategy.SEND_ALLOWED)
if CONTEXTS not in event:
event[CONTEXTS] = {}
if CONTEXTS not in event:
event[CONTEXTS] = {}

if TAGS not in event:
event[TAGS] = {}
if TAGS not in event:
event[TAGS] = {}

event[CONTEXTS][REPORTER] = {}
event[CONTEXTS][REPORTER] = {}

# tags
tags = event[TAGS]
tags['version'] = get_value(post_data, 'version')
tags['machine'] = get_value(post_data, 'machine')
tags['os'] = get_value(post_data, 'os')
tags['platform'] = get_first_item(get_value(sys_info, 'platform'))
tags[('%s' % PLATFORM_DETAILS)] = get_first_item(get_value(sys_info, PLATFORM_DETAILS))
# tags
tags = event[TAGS]
tags['version'] = get_value(post_data, 'version')
tags['machine'] = get_value(post_data, 'machine')
tags['os'] = get_value(post_data, 'os')
tags['platform'] = get_first_item(get_value(sys_info, 'platform'))
tags[('%s' % PLATFORM_DETAILS)] = get_first_item(get_value(sys_info, PLATFORM_DETAILS))

# context
context = event[CONTEXTS]
reporter = context[REPORTER]
version = get_value(post_data, 'version')
# context
context = event[CONTEXTS]
reporter = context[REPORTER]
version = get_value(post_data, 'version')

context['browser'] = {'version': version, 'name': 'Tribler'}
context['browser'] = {'version': version, 'name': 'Tribler'}

reporter[STACKTRACE] = parse_stacktrace(get_value(post_data, 'stack'))
reporter['comments'] = get_value(post_data, 'comments')
reporter[STACKTRACE] = parse_stacktrace(get_value(post_data, 'stack'))
reporter['comments'] = get_value(post_data, 'comments')

reporter[OS_ENVIRON] = parse_os_environ(get_value(sys_info, OS_ENVIRON))
delete_item(sys_info, OS_ENVIRON)
reporter[SYSINFO] = sys_info
reporter[OS_ENVIRON] = parse_os_environ(get_value(sys_info, OS_ENVIRON))
delete_item(sys_info, OS_ENVIRON)
reporter[SYSINFO] = sys_info

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

return event
finally:
SentryReporter.strategy.set(saved_strategy)
return event

@staticmethod
def get_confirmation(exception):
Expand Down Expand Up @@ -254,13 +260,9 @@ def event_from_exception(exception):
if not exception:
return exception

saved_strategy = SentryReporter.strategy.get()
try:
SentryReporter.strategy.set(SentryReporter.Strategy.SEND_SUPPRESSED)
with this_sentry_strategy(SentryStrategy.SEND_SUPPRESSED):
sentry_sdk.capture_exception(exception)
return SentryReporter.last_event
finally:
SentryReporter.strategy.set(saved_strategy)

@staticmethod
def set_user(user_id):
Expand Down Expand Up @@ -290,6 +292,18 @@ def set_user(user_id):
sentry_sdk.set_user(user)
return user

@staticmethod
def get_actual_strategy():
"""This method is used to determine actual strategy.
Strategy can be global: SentryReporter.strategy
and local: SentryReporter._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

@staticmethod
def _before_send(event, hint):
"""The method that is called before each send. Both allowed and
Expand All @@ -310,7 +324,9 @@ def _before_send(event, hint):
if not event:
return event

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

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

exc_info = get_value(hint, 'exc_info')
Expand All @@ -320,19 +336,19 @@ def _before_send(event, hint):
SentryReporter._logger.debug(f"Exception is in ignored: {hint}. Skipped.")
return None

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

if strategy == SentryReporter.Strategy.SEND_ALLOWED_WITH_CONFIRMATION:
if strategy == SentryStrategy.SEND_ALLOWED_WITH_CONFIRMATION:
SentryReporter._logger.debug("Request confirmation.")
if not SentryReporter.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)
SentryReporter._logger.debug(f"Clean up the event with scrubber: {SentryReporter.scrubber}")
if SentryReporter.scrubber:
event = SentryReporter.scrubber.scrub_event(event)

return event

0 comments on commit 73a8f73

Please sign in to comment.