249 lines
7 KiB
JavaScript
249 lines
7 KiB
JavaScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {Platform} from 'react-native';
|
|
import {Sentry} from 'react-native-sentry';
|
|
|
|
import Config from 'assets/config';
|
|
|
|
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
|
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
|
import {getCurrentTeam, getCurrentTeamMembership} from 'mattermost-redux/selectors/entities/teams';
|
|
import {getCurrentChannel, getMyCurrentChannelMembership} from 'mattermost-redux/selectors/entities/channels';
|
|
|
|
export const LOGGER_EXTENSION = 'extension';
|
|
export const LOGGER_JAVASCRIPT = 'javascript';
|
|
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
|
|
Sentry.config('');
|
|
|
|
return;
|
|
}
|
|
|
|
const dsn = getDsn();
|
|
|
|
if (!dsn) {
|
|
console.warn('Sentry is enabled, but not configured on this platform'); // eslint-disable-line no-console
|
|
}
|
|
|
|
Sentry.config(dsn, Config.SentryOptions).install();
|
|
}
|
|
|
|
function getDsn() {
|
|
if (Platform.OS === 'android') {
|
|
return Config.SentryDsnAndroid;
|
|
} else if (Platform.OS === 'ios') {
|
|
return Config.SentryDsnIos;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
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) {
|
|
if (!Config.SentryEnabled) {
|
|
return;
|
|
}
|
|
|
|
if (err && logger) {
|
|
try {
|
|
Sentry.captureException(err, {logger});
|
|
} catch (error) {
|
|
// do nothing...
|
|
}
|
|
}
|
|
}
|
|
|
|
export function captureMessage(message, logger, store) {
|
|
if (message && logger && store) {
|
|
capture(() => {
|
|
Sentry.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 = obj.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 match = (/^(?:https?:\/\/)[^/]+(\/.*)$/);
|
|
|
|
if (match && match.length >= 2) {
|
|
breadcrumb.data.url = match[1];
|
|
}
|
|
}
|
|
|
|
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) {
|
|
if (!Config.SentryEnabled) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const state = store.getState();
|
|
const config = getConfig(state);
|
|
|
|
// Don't contact Sentry if we're connected to a server with diagnostics disabled. Note that this will
|
|
// still log if we're not connected to any server.
|
|
if (config.EnableDiagnostics != null && config.EnableDiagnostics !== 'true') {
|
|
return;
|
|
}
|
|
|
|
Sentry.setUserContext(getUserContext(state));
|
|
Sentry.setExtraContext(getExtraContext(state));
|
|
Sentry.setTagsContext(getBuildTags(state));
|
|
|
|
console.warn('Capturing with Sentry at ' + getDsn() + '...'); // eslint-disable-line no-console
|
|
|
|
captureFunc();
|
|
} catch (e) {
|
|
// Don't want this to get into an infinite loop again...
|
|
console.warn('Exception occured while sending to Sentry'); // eslint-disable-line no-console
|
|
console.warn(e); // eslint-disable-line no-console
|
|
}
|
|
}
|
|
|
|
function getUserContext(state) {
|
|
const currentUser = getCurrentUser(state);
|
|
|
|
if (!currentUser) {
|
|
return {};
|
|
}
|
|
|
|
return {
|
|
userID: currentUser.id, // This can be changed to id after we upgrade to Sentry >= 0.14.10,
|
|
email: '',
|
|
username: '',
|
|
extra: {
|
|
locale: currentUser.locale,
|
|
roles: currentUser.roles,
|
|
},
|
|
};
|
|
}
|
|
|
|
function getExtraContext(state) {
|
|
const context = {};
|
|
|
|
const currentTeam = getCurrentTeam(state);
|
|
if (currentTeam) {
|
|
context.currentTeam = {
|
|
id: currentTeam.id,
|
|
};
|
|
}
|
|
|
|
const currentTeamMember = getCurrentTeamMembership(state);
|
|
if (currentTeamMember) {
|
|
context.currentTeamMember = {
|
|
roles: currentTeamMember.roles,
|
|
};
|
|
}
|
|
|
|
const currentChannel = getCurrentChannel(state);
|
|
if (currentChannel) {
|
|
context.currentChannel = {
|
|
id: currentChannel.id,
|
|
type: currentChannel.type,
|
|
};
|
|
}
|
|
|
|
const currentChannelMember = getMyCurrentChannelMembership(state);
|
|
if (currentChannelMember) {
|
|
context.currentChannelMember = {
|
|
roles: currentChannelMember.roles,
|
|
};
|
|
}
|
|
|
|
const config = getConfig(state);
|
|
if (config) {
|
|
context.config = {
|
|
BuildDate: config.BuildDate,
|
|
BuildEnterpriseReady: config.BuildEnterpriseReady,
|
|
BuildHash: config.BuildHash,
|
|
BuildHashEnterprise: config.BuildHashEnterprise,
|
|
BuildNumber: config.BuildNumber,
|
|
};
|
|
}
|
|
|
|
return context;
|
|
}
|
|
|
|
function getBuildTags(state) {
|
|
const tags = {};
|
|
|
|
const config = getConfig(state);
|
|
if (config) {
|
|
tags.serverBuildHash = config.BuildHash;
|
|
tags.serverBuildNumber = config.BuildNumber;
|
|
}
|
|
|
|
return tags;
|
|
}
|