MM-11477 Attempt to capture uncaught redux errors as Sentry breadcrumbs (#1974)
This commit is contained in:
parent
8ab2373d7b
commit
b01849be39
6 changed files with 90 additions and 15 deletions
|
|
@ -32,7 +32,8 @@
|
|||
"expect": true,
|
||||
"it": true,
|
||||
"jest": true,
|
||||
"test": true
|
||||
"test": true,
|
||||
"__DEV__": true
|
||||
},
|
||||
"rules": {
|
||||
"array-bracket-spacing": [2, "never"],
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ export default {
|
|||
}
|
||||
},
|
||||
isTrustedDevice: () => {
|
||||
if (__DEV__) { //eslint-disable-line no-undef
|
||||
if (__DEV__) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export default {
|
|||
}
|
||||
},
|
||||
isTrustedDevice: () => {
|
||||
if (__DEV__) { //eslint-disable-line no-undef
|
||||
if (__DEV__) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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'],
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
1
index.js
1
index.js
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in a new issue