Skip to content

Commit 063e0b2

Browse files
authored
Merge pull request #8 from getsentry/feature/improve-stacktrace-merging
Improve stacktrace merging
2 parents c2eec55 + 2cca4f3 commit 063e0b2

File tree

3 files changed

+118
-32
lines changed

3 files changed

+118
-32
lines changed

docs/index.rst

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,13 @@ These are functions you can call in your javascript code:
142142
// disable stacktrace merging
143143
Sentry.config("___DSN___", {
144144
deactivateStacktraceMerging: true,
145-
logLevel: SentryLog.Debug
145+
logLevel: SentryLog.Debug,
146+
// These two options will only be considered if stacktrace merging is active
147+
// Here you can add modules that should be ignored or exclude modules
148+
// that should no longer be ignored from stacktrace merging
149+
// ignoreModulesExclude: ["I18nManager"], // Exclude is always stronger than include
150+
// ignoreModulesInclude: ["RNSentry"], // Include modules that should be ignored too
151+
// ---------------------------------
146152
}).install();
147153

148154
// export an extra context

ios/RNSentry.m

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,55 @@ + (void)installWithRootView:(RCTRootView *)rootView {
1616
[[rootView.bridge moduleForName:@"ExceptionsManager"] initWithDelegate:sentry];
1717
}
1818

19+
+ (NSNumberFormatter *)numberFormatter {
20+
static dispatch_once_t onceToken;
21+
static NSNumberFormatter *formatter = nil;
22+
dispatch_once(&onceToken, ^{
23+
formatter = [NSNumberFormatter new];
24+
formatter.numberStyle = NSNumberFormatterNoStyle;
25+
});
26+
return formatter;
27+
}
28+
29+
+ (NSRegularExpression *)frameRegex {
30+
static dispatch_once_t onceTokenRegex;
31+
static NSRegularExpression *regex = nil;
32+
dispatch_once(&onceTokenRegex, ^{
33+
// NSString *pattern = @"at (.+?) \\((?:(.+?):([0-9]+?):([0-9]+?))\\)"; // Regex with debugger
34+
NSString *pattern = @"(?:([^@]+)@(.+?):([0-9]+?):([0-9]+))"; // Regex without debugger
35+
regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil];
36+
});
37+
return regex;
38+
}
39+
40+
NSArray *SentryParseJavaScriptStacktrace(NSString *stacktrace) {
41+
NSNumberFormatter *formatter = [RNSentry numberFormatter];
42+
NSArray *lines = [stacktrace componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]];
43+
NSMutableArray *frames = [NSMutableArray array];
44+
for (NSString *line in lines) {
45+
NSRange searchedRange = NSMakeRange(0, [line length]);
46+
NSArray *matches = [[RNSentry frameRegex] matchesInString:line options:0 range:searchedRange];
47+
for (NSTextCheckingResult *match in matches) {
48+
NSString *matchText = [line substringWithRange:[match range]];
49+
[frames addObject:@{
50+
@"methodName": [line substringWithRange:[match rangeAtIndex:1]],
51+
@"column": [formatter numberFromString:[line substringWithRange:[match rangeAtIndex:4]]],
52+
@"lineNumber": [formatter numberFromString:[line substringWithRange:[match rangeAtIndex:3]]],
53+
@"file": [line substringWithRange:[match rangeAtIndex:2]]
54+
}];
55+
}
56+
}
57+
return frames;
58+
}
59+
1960
RCT_EXPORT_MODULE()
2061

2162
- (NSDictionary<NSString *, id> *)constantsToExport
2263
{
2364
return @{@"nativeClientAvailable": @YES};
2465
}
2566

67+
2668
RCT_EXPORT_METHOD(startWithDsnString:(NSString * _Nonnull)dsnString)
2769
{
2870
[SentryClient setShared:[[SentryClient alloc] initWithDsnString:[RCTConvert NSString:dsnString]]];
@@ -47,7 +89,7 @@ + (void)installWithRootView:(RCTRootView *)rootView {
4789
if ([param isKindOfClass:NSDictionary.class] && param[@"__sentry_stack"]) {
4890
@synchronized ([SentryClient shared]) {
4991
[[SentryClient shared] addExtra:@"__sentry_address" value:[NSNumber numberWithUnsignedInteger:callNativeModuleAddress]];
50-
[[SentryClient shared] addExtra:@"__sentry_stack" value:param[@"__sentry_stack"]];
92+
[[SentryClient shared] addExtra:@"__sentry_stack" value:SentryParseJavaScriptStacktrace([RCTConvert NSString:param[@"__sentry_stack"]])];
5193
}
5294
} else {
5395
if (param != nil) {

lib/Sentry.js

Lines changed: 68 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,49 @@
1-
import { NativeModules } from 'react-native';
1+
import {
2+
NativeModules
3+
} from 'react-native';
24
import Raven from 'raven-js';
35
require('raven-js/plugins/react-native')(Raven);
46

57
const {
68
RNSentry
79
} = NativeModules;
810

11+
const DEFAULT_MODULE_IGNORES = [
12+
"AccessibilityManager",
13+
"ActionSheetManager",
14+
"AlertManager",
15+
"AppState",
16+
"AsyncLocalStorage",
17+
"Clipboard",
18+
"DevLoadingView",
19+
"DevMenu",
20+
"ExceptionsManager",
21+
"I18nManager",
22+
"ImageEditingManager",
23+
"ImageStoreManager",
24+
"ImageViewManager",
25+
"IOSConstants",
26+
"JSCExecutor",
27+
"JSCSamplingProfiler",
28+
"KeyboardObserver",
29+
"LinkingManager",
30+
"LocationObserver",
31+
"NativeAnimatedModule",
32+
"NavigatorManager",
33+
"NetInfo",
34+
"Networking",
35+
"RedBox",
36+
"ScrollViewManager",
37+
"SettingsManager",
38+
"SourceCode",
39+
"StatusBarManager",
40+
"Timing",
41+
"UIManager",
42+
"Vibration",
43+
"WebSocketModule",
44+
"WebViewManager"
45+
];
46+
947
export const SentrySeverity = {
1048
Fatal: 0,
1149
Error: 1,
@@ -80,6 +118,14 @@ class NativeClient {
80118
if (options && options.logLevel) {
81119
RNSentry.setLogLevel(options.logLevel);
82120
}
121+
this._ignoreModulesExclude = [];
122+
if (options && options.ignoreModulesExclude) {
123+
this._ignoreModulesExclude = options.ignoreModulesExclude;
124+
}
125+
this._ignoreModulesInclude = [];
126+
if (options && options.ignoreModulesInclude) {
127+
this._ignoreModulesInclude = options.ignoreModulesInclude;
128+
}
83129
if (this._deactivateStacktraceMerging === false) {
84130
this._activateStacktraceMerging();
85131
}
@@ -115,40 +161,30 @@ class NativeClient {
115161
if (this._activatedMerging) {
116162
return;
117163
}
164+
this._ignoredModules = {};
165+
__fbBatchedBridgeConfig.remoteModuleConfig.forEach((module, moduleID) => {
166+
if (module !== null &&
167+
this._ignoreModulesExclude.indexOf(module[0]) == -1 &&
168+
(DEFAULT_MODULE_IGNORES.indexOf(module[0]) >= 0 ||
169+
this._ignoreModulesInclude.indexOf(module[0]) >= 0)) {
170+
this._ignoredModules[moduleID] = true;
171+
}
172+
});
118173
this._activatedMerging = true;
119174
this._overwriteEnqueueNativeCall();
120175
});
121176
}
122177

123178
_overwriteEnqueueNativeCall = () => {
124-
let BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
125-
let parseErrorStack = require('react-native/Libraries/Core/Devtools/parseErrorStack');
126-
let original = BatchedBridge.enqueueNativeCall;
127-
BatchedBridge.enqueueNativeCall = function(moduleID: number, methodID: number, params: Array<any>, onFail: ?Function, onSucc: ?Function) {
128-
const stack = parseErrorStack(new Error());
129-
let sendStacktrace = true;
130-
stack.forEach(function (frame) {
131-
if (frame.methodName) {
132-
const mN = frame.methodName
133-
if (mN.match("createAnimatedNode") ||
134-
mN.match("setupDevtools") ||
135-
mN.match("stopAnimation") ||
136-
mN.match("TimingAnimation") ||
137-
mN.match("startAnimatingNode") ||
138-
mN.match("AnimatedStyle") ||
139-
mN.match("_cancelLongPressDelayTimeout") ||
140-
mN.match("__startNativeAnimation") ||
141-
mN.match("extractEvents") ||
142-
mN.match("WebSocket.")) {
143-
sendStacktrace = false;
144-
}
179+
const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge');
180+
const original = BatchedBridge.enqueueNativeCall;
181+
const that = this;
182+
BatchedBridge.enqueueNativeCall = function(moduleID: number, methodID: number, params: Array < any > , onFail: ? Function, onSucc : ? Function) {
183+
if (that._ignoredModules[moduleID]) {
184+
return original.apply(this, arguments);
145185
}
146-
});
147-
148-
if (sendStacktrace) {
149-
params.push({'__sentry_stack' : stack});
150-
}
151-
return original.apply(this, arguments);
186+
params.push({ '__sentry_stack': new Error().stack });
187+
return original.apply(this, arguments);
152188
}
153189
}
154190
}
@@ -163,7 +199,9 @@ class RavenClient {
163199
if (options === null || options === undefined) {
164200
options = {};
165201
}
166-
Object.assign(options, { allowSecretKey: true });
202+
Object.assign(options, {
203+
allowSecretKey: true
204+
});
167205
Raven.config(dsn, options).install();
168206
}
169207

@@ -186,7 +224,7 @@ class RavenClient {
186224

187225
captureMessage = async(message, options) => {
188226
if (options && options.level) {
189-
switch(options.level) {
227+
switch (options.level) {
190228
case SentrySeverity.Warning:
191229
options.level = 'warning';
192230
break;

0 commit comments

Comments
 (0)