forked from Ivasoft/mattermost-mobile
217 lines
5.9 KiB
TypeScript
217 lines
5.9 KiB
TypeScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
import {Database} from '@nozbe/watermelondb';
|
|
import Sentry from '@sentry/react-native';
|
|
import {Breadcrumb, Event} from '@sentry/types';
|
|
import {Platform} from 'react-native';
|
|
import {Navigation} from 'react-native-navigation';
|
|
|
|
import Config from '@assets/config.json';
|
|
import DatabaseManager from '@database/manager';
|
|
import {getConfig} from '@queries/servers/system';
|
|
import {getCurrentUser} from '@queries/servers/user';
|
|
import {isBetaApp} from '@utils/general';
|
|
|
|
import {ClientError} from './client_error';
|
|
import {logError, logWarning} from './log';
|
|
|
|
export const BREADCRUMB_UNCAUGHT_APP_ERROR = 'uncaught-app-error';
|
|
export const BREADCRUMB_UNCAUGHT_NON_ERROR = 'uncaught-non-error';
|
|
|
|
export function initializeSentry() {
|
|
if (!Config.SentryEnabled) {
|
|
return;
|
|
}
|
|
|
|
const dsn = getDsn();
|
|
|
|
if (!dsn) {
|
|
logWarning('Sentry is enabled, but not configured on this platform');
|
|
return;
|
|
}
|
|
|
|
const mmConfig = {
|
|
environment: isBetaApp ? 'beta' : 'production',
|
|
tracesSampleRate: isBetaApp ? 1.0 : 0.2,
|
|
sampleRate: isBetaApp ? 1.0 : 0.2,
|
|
attachStacktrace: isBetaApp, // For Beta, stack traces are automatically attached to all messages logged
|
|
};
|
|
|
|
Sentry.init({
|
|
dsn,
|
|
sendDefaultPii: false,
|
|
...mmConfig,
|
|
...Config.SentryOptions,
|
|
integrations: [
|
|
new Sentry.ReactNativeTracing({
|
|
|
|
// Pass instrumentation to be used as `routingInstrumentation`
|
|
routingInstrumentation: new Sentry.ReactNativeNavigationInstrumentation(
|
|
Navigation,
|
|
),
|
|
}),
|
|
],
|
|
beforeSend: (event: Event) => {
|
|
if (isBetaApp || event?.level === 'fatal') {
|
|
return event;
|
|
}
|
|
|
|
return null;
|
|
},
|
|
});
|
|
}
|
|
|
|
function getDsn() {
|
|
if (Platform.OS === 'android') {
|
|
return Config.SentryDsnAndroid;
|
|
} else if (Platform.OS === 'ios') {
|
|
return Config.SentryDsnIos;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
export function captureException(error: Error | string) {
|
|
if (!Config.SentryEnabled) {
|
|
return;
|
|
}
|
|
|
|
if (!error) {
|
|
logWarning('captureException called with missing arguments', error);
|
|
return;
|
|
}
|
|
Sentry.captureException(error);
|
|
}
|
|
|
|
export function captureJSException(error: Error | ClientError, isFatal: boolean) {
|
|
if (!Config.SentryEnabled) {
|
|
return;
|
|
}
|
|
|
|
if (!error) {
|
|
logWarning('captureJSException called with missing arguments', error);
|
|
return;
|
|
}
|
|
|
|
if (error instanceof ClientError) {
|
|
captureClientErrorAsBreadcrumb(error, isFatal);
|
|
} else {
|
|
captureException(error);
|
|
}
|
|
}
|
|
|
|
function captureClientErrorAsBreadcrumb(error: ClientError, isFatal: boolean) {
|
|
const isAppError = Boolean(error.server_error_id);
|
|
const breadcrumb: Breadcrumb = {
|
|
category: isAppError ? BREADCRUMB_UNCAUGHT_APP_ERROR : BREADCRUMB_UNCAUGHT_NON_ERROR,
|
|
data: {
|
|
isFatal: String(isFatal),
|
|
},
|
|
level: 'warning',
|
|
};
|
|
|
|
if (error.intl?.defaultMessage) {
|
|
breadcrumb.message = error.intl.defaultMessage;
|
|
} else {
|
|
breadcrumb.message = error.message;
|
|
}
|
|
|
|
if (breadcrumb.data) {
|
|
if (error.server_error_id) {
|
|
breadcrumb.data.server_error_id = error.server_error_id;
|
|
}
|
|
|
|
if (error.status_code) {
|
|
breadcrumb.data.status_code = error.status_code;
|
|
}
|
|
|
|
const match = (/^(?:https?:\/\/)[^/]+(\/.*)$/).exec(error.url);
|
|
|
|
if (match && match.length >= 2) {
|
|
breadcrumb.data.url = match[1];
|
|
}
|
|
}
|
|
|
|
try {
|
|
Sentry.addBreadcrumb(breadcrumb);
|
|
} catch (e) {
|
|
// Do nothing since this is only here to make sure we don't crash when handling an exception
|
|
logWarning('Failed to capture breadcrumb of non-error', e);
|
|
}
|
|
}
|
|
|
|
const getUserContext = async (database: Database) => {
|
|
const currentUser = {
|
|
id: 'currentUserId',
|
|
locale: 'en',
|
|
roles: 'multi-server-test-role',
|
|
};
|
|
|
|
const user = await getCurrentUser(database);
|
|
|
|
return {
|
|
userID: user?.id ?? currentUser.id,
|
|
email: '',
|
|
username: '',
|
|
locale: user?.locale ?? currentUser.locale,
|
|
roles: user?.roles ?? currentUser.roles,
|
|
};
|
|
};
|
|
|
|
const getExtraContext = async (database: Database) => {
|
|
const context = {
|
|
config: {},
|
|
currentChannel: {},
|
|
currentTeam: {},
|
|
};
|
|
|
|
const config = await getConfig(database);
|
|
if (config) {
|
|
context.config = {
|
|
BuildDate: config.BuildDate,
|
|
BuildEnterpriseReady: config.BuildEnterpriseReady,
|
|
BuildHash: config.BuildHash,
|
|
BuildHashEnterprise: config.BuildHashEnterprise,
|
|
BuildNumber: config.BuildNumber,
|
|
};
|
|
}
|
|
|
|
return context;
|
|
};
|
|
|
|
const getBuildTags = async (database: Database) => {
|
|
const tags = {
|
|
serverBuildHash: '',
|
|
serverBuildNumber: '',
|
|
};
|
|
|
|
const config = await getConfig(database);
|
|
if (config) {
|
|
tags.serverBuildHash = config.BuildHash;
|
|
tags.serverBuildNumber = config.BuildNumber;
|
|
}
|
|
|
|
return tags;
|
|
};
|
|
|
|
export const addSentryContext = async (serverUrl: string) => {
|
|
if (!Config.SentryEnabled || !Sentry) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
|
const userContext = await getUserContext(database);
|
|
Sentry.setContext('User-Information', userContext);
|
|
|
|
const buildContext = await getBuildTags(database);
|
|
Sentry.setContext('App-Build Information', buildContext);
|
|
|
|
const extraContext = await getExtraContext(database);
|
|
Sentry.setContext('Server-Information', extraContext);
|
|
} catch (e) {
|
|
logError(`addSentryContext for serverUrl ${serverUrl}`, e);
|
|
}
|
|
};
|