Merge pull request #8 from getsentry/feature/improve-stacktrace-merging

Improve stacktrace merging
This commit is contained in:
Daniel Griesser authored and GitHub committed 2017-03-18 10:51:43 +01:00
commit 063e0b26da
3 files changed
+118 -32

No files matched your search

+7 -1
View File
@@ -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
// ignoreModulesExclude: ["I18nManager"], // Exclude is always stronger than include
// ignoreModulesInclude: ["RNSentry"], // Include modules that should be ignored too
// ---------------------------------
}).install();
// export an extra context
+43 -1
View File
@@ -16,6 +16,47 @@
[[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<NSString *, id> *)constantsToExport
@@ -23,6 +64,7 @@ RCT_EXPORT_MODULE()
return @{@"nativeClientAvailable": @YES};
}
RCT_EXPORT_METHOD(startWithDsnString:(NSString * _Nonnull)dsnString)
{
[SentryClient setShared:[[SentryClient alloc] initWithDsnString:[RCTConvert NSString:dsnString]]];
@@ -47,7 +89,7 @@ RCT_EXPORT_METHOD(activateStacktraceMerging:(RCTPromiseResolveBlock)resolve
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"]])];
}
} else {
if (param != nil) {
+68 -30
View File
@@ -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,
@@ -80,6 +118,14 @@ class NativeClient {
if (options && options.logLevel) {
RNSentry.setLogLevel(options.logLevel);
}
this._ignoreModulesExclude = [];
if (options && options.ignoreModulesExclude) {
this._ignoreModulesExclude = options.ignoreModulesExclude;
}
this._ignoreModulesInclude = [];
if (options && options.ignoreModulesInclude) {
this._ignoreModulesInclude = options.ignoreModulesInclude;
}
if (this._deactivateStacktraceMerging === false) {
this._activateStacktraceMerging();
}
@@ -115,40 +161,30 @@ class NativeClient {
if (this._activatedMerging) {
return;
}
this._ignoredModules = {};
__fbBatchedBridgeConfig.remoteModuleConfig.forEach((module, moduleID) => {
if (module !== null &&
this._ignoreModulesExclude.indexOf(module[0]) == -1 &&
(DEFAULT_MODULE_IGNORES.indexOf(module[0]) >= 0 ||
this._ignoreModulesInclude.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<any>, 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 +199,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 +224,7 @@ class RavenClient {
captureMessage = async(message, options) => {
if (options && options.level) {
switch(options.level) {
switch (options.level) {
case SentrySeverity.Warning:
options.level = 'warning';
break;