Skip to content

Commit f62a996

Browse files
committed
Initial integration of Sentry tracing, to better track stack-traces and bugs in OpenShot. Removing the old hooks and HTTP posts to openshot.org, since they would be redundant. Sentry is still gated behind our 'send_metrics' setting, just like before.
1 parent 0832cd3 commit f62a996

File tree

8 files changed

+64
-54
lines changed

8 files changed

+64
-54
lines changed

src/classes/app.py

+5-4
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import traceback
3535
import json
3636

37+
from classes import exceptions
3738
from PyQt5.QtCore import PYQT_VERSION_STR
3839
from PyQt5.QtCore import QT_VERSION_STR
3940
from PyQt5.QtCore import pyqtSlot
@@ -136,6 +137,10 @@ def __init__(self, *args, mode=None):
136137
# Set location of OpenShot program (for libopenshot)
137138
openshot.Settings.Instance().PATH_OPENSHOT_INSTALL = info.PATH
138139

140+
# Check to disable sentry
141+
if not self.settings.get('send_metrics'):
142+
exceptions.disable_sentry_tracing()
143+
139144
def show_environment(self, info, openshot):
140145
log = self.log
141146
try:
@@ -163,10 +168,6 @@ def show_environment(self, info, openshot):
163168
except Exception:
164169
log.debug("Error displaying dependency/system details", exc_info=1)
165170

166-
# Init and attach exception handler
167-
from classes import exceptions
168-
sys.excepthook = exceptions.ExceptionHandler
169-
170171
def check_libopenshot_version(self, info, openshot):
171172
"""Detect minimum libopenshot version"""
172173
_ = self._tr

src/classes/exceptions.py

+38-18
Original file line numberDiff line numberDiff line change
@@ -28,26 +28,40 @@
2828
import os
2929
import traceback
3030
import platform
31+
import distro
32+
import sentry_sdk
3133

3234
from classes import info
33-
from classes.logger import log
3435

3536

36-
def ExceptionHandler(exeception_type, exeception_value, exeception_traceback):
37-
"""Callback for any unhandled exceptions"""
38-
from classes.metrics import track_exception_stacktrace
37+
def init_sentry_tracing():
38+
"""Init all Sentry tracing"""
3939

40-
log.error(
41-
'Unhandled Exception',
42-
exc_info=(exeception_type, exeception_value, exeception_traceback))
40+
# Determine sample rate for exceptions
41+
traces_sample_rate = 0.1
42+
environment = "production"
43+
if "-dev" in info.VERSION:
44+
# Dev mode, trace all exceptions
45+
traces_sample_rate = 1.0
46+
environment = "development"
4347

44-
# Build string of stack trace
45-
stacktrace = "Python %s" % "".join(
46-
traceback.format_exception(
47-
exeception_type, exeception_value, exeception_traceback))
48+
# Initialize sentry exception tracing
49+
sentry_sdk.init(
50+
"https://[email protected]/5795985",
51+
traces_sample_rate=traces_sample_rate,
52+
release=f"openshot@{info.VERSION}",
53+
environment=environment
54+
)
55+
sentry_sdk.set_tag("system", platform.system())
56+
sentry_sdk.set_tag("machine", platform.machine())
57+
sentry_sdk.set_tag("processor", platform.processor())
58+
sentry_sdk.set_tag("platform", platform.platform())
59+
sentry_sdk.set_tag("distro", " ".join(distro.linux_distribution()))
4860

49-
# Report traceback to webservice (if enabled)
50-
track_exception_stacktrace(stacktrace, "openshot-qt")
61+
62+
def disable_sentry_tracing():
63+
"""Disable all Sentry tracing requests"""
64+
sentry_sdk.init()
5165

5266

5367
def tail_file(f, n, offset=None):
@@ -72,7 +86,7 @@ def tail_file(f, n, offset=None):
7286

7387
def libopenshot_crash_recovery():
7488
"""Walk libopenshot.log for the last line before this launch"""
75-
from classes.metrics import track_exception_stacktrace, track_metric_error
89+
from classes.metrics import track_metric_error
7690

7791
log_path = os.path.join(info.USER_PATH, "libopenshot.log")
7892
last_log_line = ""
@@ -84,7 +98,8 @@ def libopenshot_crash_recovery():
8498
with open(log_path, "rb") as f:
8599
# Read from bottom up
86100
for raw_line in reversed(tail_file(f, 500)):
87-
line = str(raw_line, 'utf-8')
101+
# Format and remove extra spaces from line
102+
line = " ".join(str(raw_line, 'utf-8').split()) + "\n"
88103
# Detect stack trace
89104
if "End of Stack Trace" in line:
90105
found_stack = True
@@ -113,10 +128,13 @@ def libopenshot_crash_recovery():
113128
# Split last stack trace (if any)
114129
if last_stack_trace:
115130
# Get top line of stack trace (for metrics)
116-
last_log_line = last_stack_trace.split("\n")[0].strip()
131+
exception_lines = last_stack_trace.split("\n")
132+
last_log_line = exception_lines[0].strip()
117133

118-
# Send stacktrace for debugging (if send metrics is enabled)
119-
track_exception_stacktrace(last_stack_trace, "libopenshot")
134+
# Format and add exception log to Sentry context
135+
# Split list of lines into smaller lists (so we don't exceed Sentry limits)
136+
sentry_sdk.set_context("libopenshot", {"stack-trace": exception_lines})
137+
sentry_sdk.set_tag("component", "libopenshot")
120138

121139
# Clear / normalize log line (so we can roll them up in the analytics)
122140
if last_log_line:
@@ -142,3 +160,5 @@ def libopenshot_crash_recovery():
142160

143161
# Report exception (with last libopenshot line... if found)
144162
track_metric_error("unhandled-crash%s" % last_log_line, True)
163+
164+
return last_log_line

src/classes/info.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
import os
2929
from time import strftime
3030

31-
VERSION = "2.5.1-dev2"
31+
VERSION = "2.5.1-dev3"
3232
MINIMUM_LIBOPENSHOT_VERSION = "0.2.5"
3333
DATE = "20200228000000"
3434
NAME = "openshot-qt"

src/classes/language.py

+2
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929
import os
3030
import locale
31+
import sentry_sdk
3132

3233
from PyQt5.QtCore import QLocale, QLibraryInfo, QTranslator, QCoreApplication
3334

@@ -119,6 +120,7 @@ def init_language():
119120
if found_language:
120121
log.debug("Exiting translation system (since we successfully loaded: {})".format(locale_name))
121122
info.CURRENT_LANGUAGE = locale_name
123+
sentry_sdk.set_tag("locale", locale_name)
122124
break
123125

124126

src/classes/metrics.py

-27
Original file line numberDiff line numberDiff line change
@@ -133,13 +133,6 @@ def track_metric_error(error_name, is_fatal=False):
133133
t.start()
134134

135135

136-
def track_exception_stacktrace(stacktrace, source):
137-
"""Track an exception/stacktrace has occurred"""
138-
t = threading.Thread(target=send_exception, args=[stacktrace, source])
139-
t.daemon = True
140-
t.start()
141-
142-
143136
def track_metric_session(is_start=True):
144137
"""Track a GUI screen being shown"""
145138
metric_params = deepcopy(params)
@@ -183,23 +176,3 @@ def send_metric(params):
183176
# All metrics have been sent (or attempted to send)
184177
# Clear the queue
185178
metric_queue.clear()
186-
187-
188-
def send_exception(stacktrace, source):
189-
"""Send exception stacktrace over HTTP for tracking"""
190-
# Check if the user wants to send metrics and errors
191-
if s.get("send_metrics"):
192-
193-
data = urllib.parse.urlencode({"stacktrace": stacktrace,
194-
"platform": platform.system(),
195-
"version": info.VERSION,
196-
"source": source,
197-
"unique_install_id": s.get("unique_install_id")})
198-
url = "http://www.openshot.org/exception/json/"
199-
200-
# Send exception HTTP data
201-
try:
202-
r = requests.post(url, data=data, headers={"user-agent": user_agent, "content-type": "application/x-www-form-urlencoded"}, verify=False)
203-
log.info("Track exception: [%s] %s | %s", r.status_code, r.url, r.text)
204-
except Exception:
205-
log.warning("Failed to track exception", exc_info=1)

src/launch.py

+5-2
Original file line numberDiff line numberDiff line change
@@ -70,11 +70,14 @@
7070
pass # Quietly fail for older Qt5 versions
7171

7272
try:
73-
from classes import info
73+
from classes import info, exceptions
7474
except ImportError:
7575
import openshot_qt
7676
sys.path.append(openshot_qt.OPENSHOT_PATH)
77-
from classes import info
77+
from classes import info, exceptions
78+
79+
# Initialize sentry exception tracing
80+
exceptions.init_sentry_tracing()
7881

7982
# Global holder for QApplication instance
8083
app = None

src/windows/main_window.py

+10-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
import os
3131
import shutil
32+
import sentry_sdk
3233
import webbrowser
3334
from copy import deepcopy
3435
from time import sleep
@@ -219,13 +220,17 @@ def create_lock_file(self):
219220
lock_path = os.path.join(info.USER_PATH, ".lock")
220221
# Check if it already exists
221222
if os.path.exists(lock_path):
222-
exceptions.libopenshot_crash_recovery()
223-
log.error("Unhandled crash detected. Preserving cache.")
223+
last_log_line = exceptions.libopenshot_crash_recovery() or "No Log Detected"
224+
log.error(f"Unhandled crash detected: {last_log_line}")
224225
self.destroy_lock_file()
225226
else:
226227
# Normal startup, clear thumbnails
227228
self.clear_all_thumbnails()
228229

230+
# Reset Sentry component (it can be temporarily changed to libopenshot during
231+
# the call to libopenshot_crash_recovery above)
232+
sentry_sdk.set_tag("component", "openshot-qt")
233+
229234
# Write lock file (try a few times if failure)
230235
lock_value = str(uuid4())
231236
for attempt in range(5):
@@ -2804,6 +2809,9 @@ def __init__(self, *args, mode=None):
28042809
# Track 1st launch metric
28052810
track_metric_screen("initial-launch-screen")
28062811

2812+
# Set unique id for Sentry
2813+
sentry_sdk.set_user({"id": s.get("unique_install_id")})
2814+
28072815
# Track main screen
28082816
track_metric_screen("main-screen")
28092817

src/windows/views/tutorial.py

+3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from classes.logger import log
3939
from classes.app import get_app
4040
from classes.metrics import track_metric_screen
41+
from classes.exceptions import init_sentry_tracing, disable_sentry_tracing
4142

4243

4344
class TutorialDialog(QWidget):
@@ -70,12 +71,14 @@ def checkbox_metrics_callback(self, state):
7071
if state == Qt.Checked:
7172
# Enabling metrics sending
7273
s.set("send_metrics", True)
74+
init_sentry_tracing()
7375

7476
# Opt-in for metrics tracking
7577
track_metric_screen("metrics-opt-in")
7678
else:
7779
# Opt-out for metrics tracking
7880
track_metric_screen("metrics-opt-out")
81+
disable_sentry_tracing()
7982

8083
# Disable metric sending
8184
s.set("send_metrics", False)

0 commit comments

Comments
 (0)