diff --git a/src/sentry/interfaces/stacktrace.py b/src/sentry/interfaces/stacktrace.py index 45fd483d518295..483fea890a99c4 100644 --- a/src/sentry/interfaces/stacktrace.py +++ b/src/sentry/interfaces/stacktrace.py @@ -39,6 +39,12 @@ (\$\$[\w_]+?CGLIB\$\$)[a-fA-F0-9]+(_[0-9]+)? ''', re.X) +# React-Native: +# file:///var/containers/Bundle/Application/{DEVICE_ID}/HelloWorld.app/main.jsbundle +# Electron: +# file:///x/yy/zzz/Electron.app/Contents/app.asar/file1.js +_js_native_path_re = re.compile(r'^(file\:\/\/)?/.*\/[^\.\/]+(\.app|CodePush|\.app/Contents/Resources/app(\.asar)?/|/resources/app(\.asar)?/)\/') + def trim_package(pkg): if not pkg: @@ -219,6 +225,11 @@ def handle_nan(value): return value +def strip_js_native_components(value): + # we maintain the leading prefix for compat + return _js_native_path_re.sub('/', value) + + class Frame(Interface): @classmethod def to_python(cls, data): @@ -234,6 +245,10 @@ def to_python(cls, data): if v is not None and not isinstance(v, six.string_types): raise InterfaceValidationError("Invalid value for '%s'" % name) + # JS sdk only sends filename + if filename and filename.startswith('file://'): + filename = strip_js_native_components(filename) + # absolute path takes priority over filename # (in the end both will get set) if not abs_path: diff --git a/tests/sentry/interfaces/test_stacktrace.py b/tests/sentry/interfaces/test_stacktrace.py index c9470e34da3a95..7687efff051311 100644 --- a/tests/sentry/interfaces/test_stacktrace.py +++ b/tests/sentry/interfaces/test_stacktrace.py @@ -103,6 +103,46 @@ def test_ignores_results_with_empty_path(self): assert frame.filename == 'http://foo.com' assert frame.abs_path == frame.filename + def test_normalizes_react_native_filename(self): + interface = Stacktrace.to_python(dict(frames=[{ + 'lineno': 1, + 'filename': 'file:///var/containers/Bundle/Application/06C10E06-2ABE-41A3-B7F2-3B7452C057B1/HelloWorld.app/main.jsbundle', + }])) + frame = interface.frames[0] + assert frame.filename == '/main.jsbundle' + + def test_normalizes_electron_mac_asar_filename(self): + interface = Stacktrace.to_python(dict(frames=[{ + 'lineno': 1, + 'filename': 'file:///x/yy/zzz/Electron.app/Contents/app.asar/file1.js', + }])) + frame = interface.frames[0] + assert frame.filename == '/file1.js' + + def test_normalizes_electron_mac_filename(self): + interface = Stacktrace.to_python(dict(frames=[{ + 'lineno': 1, + 'filename': 'file:///x/yy/zzz/Electron.app/Contents/app/file1.js', + }])) + frame = interface.frames[0] + assert frame.filename == '/file1.js' + + def test_normalizes_electron_windows_filename(self): + interface = Stacktrace.to_python(dict(frames=[{ + 'lineno': 1, + 'filename': 'file:///x/yy/zzz/Electron/resources/app/file1.js', + }])) + frame = interface.frames[0] + assert frame.filename == '/file1.js' + + def test_normalizes_electron_windows_asar_filename(self): + interface = Stacktrace.to_python(dict(frames=[{ + 'lineno': 1, + 'filename': 'file:///x/yy/zzz/Electron/resources/app.asar/file1.js', + }])) + frame = interface.frames[0] + assert frame.filename == '/file1.js' + def test_serialize_returns_frames(self): interface = Stacktrace.to_python(dict(frames=[{ 'lineno': 1,