From 0242f3e067caf0b0104b7480509b74380ada9b27 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 17 Mar 2017 18:13:17 +0100 Subject: [PATCH 1/4] Improve performance in stacktrace merging --- ios/RNSentry.m | 45 +++++++++++++++++++++++++- lib/Sentry.js | 87 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 101 insertions(+), 31 deletions(-) diff --git a/ios/RNSentry.m b/ios/RNSentry.m index fd123b87ff..ba76d05e5c 100644 --- a/ios/RNSentry.m +++ b/ios/RNSentry.m @@ -16,6 +16,47 @@ + (void)installWithRootView:(RCTRootView *)rootView { [[rootView.bridge moduleForName:@"ExceptionsManager"] initWithDelegate:sentry]; } ++ (NSNumberFormatter *)numberFormatter { + static dispatch_once_t onceToken; + static NSNumberFormatter *formatter = nil; + dispatch_once(&onceToken, ^{ + formatter = [NSNumberFormatter new]; + formatter.numberStyle = NSNumberFormatterNoStyle; + }); + return formatter; +} + ++ (NSRegularExpression *)frameRegex { + static dispatch_once_t onceTokenRegex; + static NSRegularExpression *regex = nil; + dispatch_once(&onceTokenRegex, ^{ +// NSString *pattern = @"at (.+?) \\((?:(.+?):([0-9]+?):([0-9]+?))\\)"; // Regex with debugger + NSString *pattern = @"(?:([^@]+)@(.+?):([0-9]+?):([0-9]+))"; // Regex without debugger + regex = [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:nil]; + }); + return regex; +} + +NSArray *SentryParseJavaScriptStacktrace(NSString *stacktrace) { + NSNumberFormatter *formatter = [RNSentry numberFormatter]; + NSArray *lines = [stacktrace componentsSeparatedByCharactersInSet:[NSCharacterSet newlineCharacterSet]]; + NSMutableArray *frames = [NSMutableArray array]; + for (NSString *line in lines) { + NSRange searchedRange = NSMakeRange(0, [line length]); + NSArray *matches = [[RNSentry frameRegex] matchesInString:line options:0 range:searchedRange]; + for (NSTextCheckingResult *match in matches) { + NSString *matchText = [line substringWithRange:[match range]]; + [frames addObject:@{ + @"methodName": [line substringWithRange:[match rangeAtIndex:1]], + @"column": [formatter numberFromString:[line substringWithRange:[match rangeAtIndex:4]]], + @"lineNumber": [formatter numberFromString:[line substringWithRange:[match rangeAtIndex:3]]], + @"file": [line substringWithRange:[match rangeAtIndex:2]] + }]; + } + } + return frames; +} + RCT_EXPORT_MODULE() - (NSDictionary *)constantsToExport @@ -23,6 +64,7 @@ + (void)installWithRootView:(RCTRootView *)rootView { return @{@"nativeClientAvailable": @YES}; } + RCT_EXPORT_METHOD(startWithDsnString:(NSString * _Nonnull)dsnString) { [SentryClient setShared:[[SentryClient alloc] initWithDsnString:[RCTConvert NSString:dsnString]]]; @@ -47,7 +89,8 @@ + (void)installWithRootView:(RCTRootView *)rootView { if ([param isKindOfClass:NSDictionary.class] && param[@"__sentry_stack"]) { @synchronized ([SentryClient shared]) { [[SentryClient shared] addExtra:@"__sentry_address" value:[NSNumber numberWithUnsignedInteger:callNativeModuleAddress]]; - [[SentryClient shared] addExtra:@"__sentry_stack" value:param[@"__sentry_stack"]]; + [[SentryClient shared] addExtra:@"__sentry_stack" value:SentryParseJavaScriptStacktrace([RCTConvert NSString:param[@"__sentry_stack"]])]; + NSLog(@"%@", SentryParseJavaScriptStacktrace([RCTConvert NSString:param[@"__sentry_stack"]])); } } else { if (param != nil) { diff --git a/lib/Sentry.js b/lib/Sentry.js index d8a14eff9a..d473fa7c4f 100644 --- a/lib/Sentry.js +++ b/lib/Sentry.js @@ -1,4 +1,6 @@ -import { NativeModules } from 'react-native'; +import { + NativeModules +} from 'react-native'; import Raven from 'raven-js'; require('raven-js/plugins/react-native')(Raven); @@ -6,6 +8,42 @@ const { RNSentry } = NativeModules; +const DEFAULT_MODULE_IGNORES = [ + "AccessibilityManager", + "ActionSheetManager", + "AlertManager", + "AppState", + "AsyncLocalStorage", + "Clipboard", + "DevLoadingView", + "DevMenu", + "ExceptionsManager", + "I18nManager", + "ImageEditingManager", + "ImageStoreManager", + "ImageViewManager", + "IOSConstants", + "JSCExecutor", + "JSCSamplingProfiler", + "KeyboardObserver", + "LinkingManager", + "LocationObserver", + "NativeAnimatedModule", + "NavigatorManager", + "NetInfo", + "Networking", + "RedBox", + "ScrollViewManager", + "SettingsManager", + "SourceCode", + "StatusBarManager", + "Timing", + "UIManager", + "Vibration", + "WebSocketModule", + "WebViewManager" +]; + export const SentrySeverity = { Fatal: 0, Error: 1, @@ -115,40 +153,27 @@ class NativeClient { if (this._activatedMerging) { return; } + this._ignoredModules = {}; + __fbBatchedBridgeConfig.remoteModuleConfig.forEach((module, moduleID) => { + if (module !== null && DEFAULT_MODULE_IGNORES.indexOf(module[0]) >= 0) { + this._ignoredModules[moduleID] = true; + } + }); this._activatedMerging = true; this._overwriteEnqueueNativeCall(); }); } _overwriteEnqueueNativeCall = () => { - let BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge'); - let parseErrorStack = require('react-native/Libraries/Core/Devtools/parseErrorStack'); - let original = BatchedBridge.enqueueNativeCall; - BatchedBridge.enqueueNativeCall = function(moduleID: number, methodID: number, params: Array, onFail: ?Function, onSucc: ?Function) { - const stack = parseErrorStack(new Error()); - let sendStacktrace = true; - stack.forEach(function (frame) { - if (frame.methodName) { - const mN = frame.methodName - if (mN.match("createAnimatedNode") || - mN.match("setupDevtools") || - mN.match("stopAnimation") || - mN.match("TimingAnimation") || - mN.match("startAnimatingNode") || - mN.match("AnimatedStyle") || - mN.match("_cancelLongPressDelayTimeout") || - mN.match("__startNativeAnimation") || - mN.match("extractEvents") || - mN.match("WebSocket.")) { - sendStacktrace = false; - } + const BatchedBridge = require('react-native/Libraries/BatchedBridge/BatchedBridge'); + const original = BatchedBridge.enqueueNativeCall; + const that = this; + BatchedBridge.enqueueNativeCall = function(moduleID: number, methodID: number, params: Array < any > , onFail: ? Function, onSucc : ? Function) { + if (that._ignoredModules[moduleID]) { + return original.apply(this, arguments); } - }); - - if (sendStacktrace) { - params.push({'__sentry_stack' : stack}); - } - return original.apply(this, arguments); + params.push({ '__sentry_stack': new Error().stack }); + return original.apply(this, arguments); } } } @@ -163,7 +188,9 @@ class RavenClient { if (options === null || options === undefined) { options = {}; } - Object.assign(options, { allowSecretKey: true }); + Object.assign(options, { + allowSecretKey: true + }); Raven.config(dsn, options).install(); } @@ -186,7 +213,7 @@ class RavenClient { captureMessage = async(message, options) => { if (options && options.level) { - switch(options.level) { + switch (options.level) { case SentrySeverity.Warning: options.level = 'warning'; break; From 35c94c45a3283fb5965a45758b13fefd325e21d1 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 17 Mar 2017 19:39:33 +0100 Subject: [PATCH 2/4] Add docs and include exclude modules --- docs/index.rst | 8 +++++++- lib/Sentry.js | 13 ++++++++++++- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index d5627a4d01..9c5c9026e3 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -142,7 +142,13 @@ These are functions you can call in your javascript code: // disable stacktrace merging Sentry.config("___DSN___", { deactivateStacktraceMerging: true, - logLevel: SentryLog.Debug + logLevel: SentryLog.Debug, + // These two options will only be considered if stacktrace merging is active + // Here you can add modules that should be ignored or exclude modules + // that should no longer be ignored from stacktrace merging + // ignore_modules_exclude: ["I18nManager"], // Exclude is always stronger than include + // ignore_modules_include: ["RNSentry"], // Include modules that should be ignored too + // --------------------------------- }).install(); // export an extra context diff --git a/lib/Sentry.js b/lib/Sentry.js index d473fa7c4f..be9d3f60e0 100644 --- a/lib/Sentry.js +++ b/lib/Sentry.js @@ -118,6 +118,14 @@ class NativeClient { if (options && options.logLevel) { RNSentry.setLogLevel(options.logLevel); } + this._ignore_modules_exclude = []; + if (options && options.ignore_modules_exclude) { + this._ignore_modules_exclude = options.ignore_modules_exclude; + } + this._ignore_modules_include = []; + if (options && options.ignore_modules_include) { + this._ignore_modules_include = options.ignore_modules_include; + } if (this._deactivateStacktraceMerging === false) { this._activateStacktraceMerging(); } @@ -155,7 +163,10 @@ class NativeClient { } this._ignoredModules = {}; __fbBatchedBridgeConfig.remoteModuleConfig.forEach((module, moduleID) => { - if (module !== null && DEFAULT_MODULE_IGNORES.indexOf(module[0]) >= 0) { + if (module !== null && + this._ignore_modules_exclude.indexOf(module[0]) == -1 && + (DEFAULT_MODULE_IGNORES.indexOf(module[0]) >= 0 || + this._ignore_modules_include.indexOf(module[0]) >= 0)) { this._ignoredModules[moduleID] = true; } }); From 4f6818f9d7a11b710caab40f00516e84a73b6153 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 17 Mar 2017 19:42:13 +0100 Subject: [PATCH 3/4] Remove nslog --- ios/RNSentry.m | 1 - 1 file changed, 1 deletion(-) diff --git a/ios/RNSentry.m b/ios/RNSentry.m index ba76d05e5c..9e022ce81a 100644 --- a/ios/RNSentry.m +++ b/ios/RNSentry.m @@ -90,7 +90,6 @@ + (NSRegularExpression *)frameRegex { @synchronized ([SentryClient shared]) { [[SentryClient shared] addExtra:@"__sentry_address" value:[NSNumber numberWithUnsignedInteger:callNativeModuleAddress]]; [[SentryClient shared] addExtra:@"__sentry_stack" value:SentryParseJavaScriptStacktrace([RCTConvert NSString:param[@"__sentry_stack"]])]; - NSLog(@"%@", SentryParseJavaScriptStacktrace([RCTConvert NSString:param[@"__sentry_stack"]])); } } else { if (param != nil) { From 2cca4f36794b520970956812a216ea5dc8ff2504 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 17 Mar 2017 21:42:32 +0100 Subject: [PATCH 4/4] Change naming --- docs/index.rst | 4 ++-- lib/Sentry.js | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 9c5c9026e3..274e06e1b0 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -146,8 +146,8 @@ These are functions you can call in your javascript code: // These two options will only be considered if stacktrace merging is active // Here you can add modules that should be ignored or exclude modules // that should no longer be ignored from stacktrace merging - // ignore_modules_exclude: ["I18nManager"], // Exclude is always stronger than include - // ignore_modules_include: ["RNSentry"], // Include modules that should be ignored too + // ignoreModulesExclude: ["I18nManager"], // Exclude is always stronger than include + // ignoreModulesInclude: ["RNSentry"], // Include modules that should be ignored too // --------------------------------- }).install(); diff --git a/lib/Sentry.js b/lib/Sentry.js index be9d3f60e0..ec4d109647 100644 --- a/lib/Sentry.js +++ b/lib/Sentry.js @@ -118,13 +118,13 @@ class NativeClient { if (options && options.logLevel) { RNSentry.setLogLevel(options.logLevel); } - this._ignore_modules_exclude = []; - if (options && options.ignore_modules_exclude) { - this._ignore_modules_exclude = options.ignore_modules_exclude; + this._ignoreModulesExclude = []; + if (options && options.ignoreModulesExclude) { + this._ignoreModulesExclude = options.ignoreModulesExclude; } - this._ignore_modules_include = []; - if (options && options.ignore_modules_include) { - this._ignore_modules_include = options.ignore_modules_include; + this._ignoreModulesInclude = []; + if (options && options.ignoreModulesInclude) { + this._ignoreModulesInclude = options.ignoreModulesInclude; } if (this._deactivateStacktraceMerging === false) { this._activateStacktraceMerging(); @@ -164,9 +164,9 @@ class NativeClient { this._ignoredModules = {}; __fbBatchedBridgeConfig.remoteModuleConfig.forEach((module, moduleID) => { if (module !== null && - this._ignore_modules_exclude.indexOf(module[0]) == -1 && + this._ignoreModulesExclude.indexOf(module[0]) == -1 && (DEFAULT_MODULE_IGNORES.indexOf(module[0]) >= 0 || - this._ignore_modules_include.indexOf(module[0]) >= 0)) { + this._ignoreModulesInclude.indexOf(module[0]) >= 0)) { this._ignoredModules[moduleID] = true; } });