MM-11477 Attempt to capture uncaught redux errors as Sentry breadcrumbs (#1974)

This commit is contained in:
Harrison Healey 2018-08-07 10:32:04 -04:00 committed by Elias Nahum
parent 8ab2373d7b
commit b01849be39
6 changed files with 90 additions and 15 deletions

View file

@ -32,7 +32,8 @@
"expect": true,
"it": true,
"jest": true,
"test": true
"test": true,
"__DEV__": true
},
"rules": {
"array-bracket-spacing": [2, "never"],

View file

@ -56,7 +56,7 @@ export default {
}
},
isTrustedDevice: () => {
if (__DEV__) { //eslint-disable-line no-undef
if (__DEV__) {
return true;
}

View file

@ -55,7 +55,7 @@ export default {
}
},
isTrustedDevice: () => {
if (__DEV__) { //eslint-disable-line no-undef
if (__DEV__) {
return true;
}

View file

@ -16,27 +16,35 @@ import {close as closeWebSocket} from 'mattermost-redux/actions/websocket';
import {purgeOfflineStore} from 'app/actions/views/root';
import {
captureException,
captureJSException,
initializeSentry,
LOGGER_JAVASCRIPT,
LOGGER_NATIVE,
} from 'app/utils/sentry';
import {app, store} from 'app/mattermost';
const errorHandler = (e, isFatal) => {
console.warn('Handling Javascript error ', e); // eslint-disable-line no-console
if (__DEV__ && !e && !isFatal) {
// react-native-exception-handler redirects console.error to call this, and React calls
// console.error without an exception when prop type validation fails, so this ends up
// being called with no arguments when the error handler is enabled in dev mode.
return;
}
console.warn('Handling Javascript error', e, isFatal); // eslint-disable-line no-console
captureJSException(e, isFatal, store);
const {dispatch} = store;
captureException(e, LOGGER_JAVASCRIPT, store);
const translations = app.getTranslations();
dispatch(closeWebSocket());
if (Client4.getUrl()) {
dispatch(logError(e));
}
if (isFatal) {
if (isFatal && e instanceof Error) {
const translations = app.getTranslations();
Alert.alert(
translations['mobile.error_handler.title'],
translations['mobile.error_handler.description'],

View file

@ -17,6 +17,9 @@ export const LOGGER_JAVASCRIPT_WARNING = 'javascript_warning';
export const LOGGER_NATIVE = 'native';
export const LOGGER_REDUX = 'redux';
export const BREADCRUMB_UNCAUGHT_APP_ERROR = 'uncaught-app-error';
export const BREADCRUMB_UNCAUGHT_NON_ERROR = 'uncaught-non-error';
export function initializeSentry() {
if (!Config.SentryEnabled) {
// Still allow Sentry to configure itself in case other code tries to call it
@ -44,12 +47,28 @@ function getDsn() {
return '';
}
export function captureException(error, logger, store) {
if (error && logger && store) {
capture(() => {
Sentry.captureException(error, {logger});
}, store);
export function captureJSException(error, isFatal, store) {
if (!error || !store) {
console.warn('captureJSException called with missing arguments', error, store); // eslint-disable-line no-console
return;
}
if (error instanceof Error) {
captureException(error, LOGGER_JAVASCRIPT, store);
} else {
captureNonErrorAsBreadcrumb(error, isFatal);
}
}
export function captureException(error, logger, store) {
if (!error || !logger || !store) {
console.warn('captureException called with missing arguments', error, logger, store); // eslint-disable-line no-console
return;
}
capture(() => {
Sentry.captureException(error, {logger});
}, store);
}
export function captureExceptionWithoutState(err, logger) {
@ -74,6 +93,54 @@ export function captureMessage(message, logger, store) {
}
}
export function captureNonErrorAsBreadcrumb(obj, isFatal) {
if (!obj || typeof obj !== 'object') {
console.warning('Invalid object passed to captureNonErrorAsBreadcrumb', obj); // eslint-disable-line no-console
return;
}
const isAppError = Boolean(obj.server_error_id);
const breadcrumb = {
category: isAppError ? BREADCRUMB_UNCAUGHT_APP_ERROR : BREADCRUMB_UNCAUGHT_NON_ERROR,
data: {
isFatal: String(isFatal),
},
level: 'warn',
};
if (obj.message) {
breadcrumb.message = obj.message;
} else if (obj.intl && obj.intl.defaultMessage) {
breadcrumb.message = breadcrumb.intl.defaultMessage;
} else {
breadcrumb.message = 'no message provided';
}
if (obj.server_error_id) {
breadcrumb.data.server_error_id = obj.server_error_id;
}
if (obj.status_code) {
breadcrumb.data.status_code = obj.status_code;
}
if (obj.url) {
const index = obj.url.indexOf('/');
if (index !== -1) {
breadcrumb.data.url = obj.url.substring(index);
}
}
try {
Sentry.captureBreadcrumb(breadcrumb);
} catch (e) {
// Do nothing since this is only here to make sure we don't crash when handling an exception
console.warn('Failed to capture breadcrumb of non-error', e); // eslint-disable-line no-console
}
}
// Wrapper function to any calls to Sentry so that we can gather any necessary extra data
// before sending.
function capture(captureFunc, store) {

View file

@ -16,7 +16,6 @@ if (Platform.OS === 'android') {
/*
/!* eslint-disable no-console *!/
/!* eslint-disable no-undef *!/
if (__DEV__) {
const modules = require.getModules();
const moduleIds = Object.keys(modules);