Automatic error reporting (#7046)

* Enable sentry global handlers if automaticErrorReporting is on

* Pass the exception through on session restore error

Passing the exception object itself through to the BugReportDialog means a stack trace can be correctly recorded in Sentry
This commit is contained in:
James Salter 2021-10-29 09:34:25 +01:00 committed by GitHub
parent d0bb6e0657
commit 3defb863b3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 37 additions and 12 deletions

View file

@ -59,6 +59,7 @@ import SessionRestoreErrorDialog from "./components/views/dialogs/SessionRestore
import StorageEvictedDialog from "./components/views/dialogs/StorageEvictedDialog"; import StorageEvictedDialog from "./components/views/dialogs/StorageEvictedDialog";
import { logger } from "matrix-js-sdk/src/logger"; import { logger } from "matrix-js-sdk/src/logger";
import { setSentryUser } from "./sentry";
const HOMESERVER_URL_KEY = "mx_hs_url"; const HOMESERVER_URL_KEY = "mx_hs_url";
const ID_SERVER_URL_KEY = "mx_is_url"; const ID_SERVER_URL_KEY = "mx_is_url";
@ -455,7 +456,7 @@ async function handleLoadSessionFailure(e: Error): Promise<boolean> {
logger.error("Unable to load session", e); logger.error("Unable to load session", e);
const modal = Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, { const modal = Modal.createTrackedDialog('Session Restore Error', '', SessionRestoreErrorDialog, {
error: e.message, error: e,
}); });
const [success] = await modal.finished; const [success] = await modal.finished;
@ -582,6 +583,8 @@ async function doSetLoggedIn(
PosthogAnalytics.instance.updateAnonymityFromSettings(credentials.userId); PosthogAnalytics.instance.updateAnonymityFromSettings(credentials.userId);
setSentryUser(credentials.userId);
const client = MatrixClientPeg.get(); const client = MatrixClientPeg.get();
if (credentials.freshLogin && SettingsStore.getValue("feature_dehydration")) { if (credentials.freshLogin && SettingsStore.getValue("feature_dehydration")) {

View file

@ -28,13 +28,15 @@ import DialogButtons from "../elements/DialogButtons";
import { IDialogProps } from "./IDialogProps"; import { IDialogProps } from "./IDialogProps";
interface IProps extends IDialogProps { interface IProps extends IDialogProps {
error: string; error: Error;
} }
@replaceableComponent("views.dialogs.SessionRestoreErrorDialog") @replaceableComponent("views.dialogs.SessionRestoreErrorDialog")
export default class SessionRestoreErrorDialog extends React.Component<IProps> { export default class SessionRestoreErrorDialog extends React.Component<IProps> {
private sendBugReport = (): void => { private sendBugReport = (): void => {
Modal.createTrackedDialog('Session Restore Error', 'Send Bug Report Dialog', BugReportDialog, {}); Modal.createTrackedDialog('Session Restore Error', 'Send Bug Report Dialog', BugReportDialog, {
error: this.props.error,
});
}; };
private onClearStorageClick = (): void => { private onClearStorageClick = (): void => {

View file

@ -92,6 +92,7 @@ export default class LabsUserSettingsTab extends React.Component<{}, IState> {
<SettingsFlag name="enableWidgetScreenshots" level={SettingLevel.ACCOUNT} /> <SettingsFlag name="enableWidgetScreenshots" level={SettingLevel.ACCOUNT} />
<SettingsFlag name="showHiddenEventsInTimeline" level={SettingLevel.DEVICE} /> <SettingsFlag name="showHiddenEventsInTimeline" level={SettingLevel.DEVICE} />
<SettingsFlag name="lowBandwidth" level={SettingLevel.DEVICE} /> <SettingsFlag name="lowBandwidth" level={SettingLevel.DEVICE} />
<SettingsFlag name="automaticErrorReporting" level={SettingLevel.DEVICE} />
{ hiddenReadReceipts } { hiddenReadReceipts }
</div>; </div>;
} }

View file

@ -890,6 +890,7 @@
"Display Communities instead of Spaces": "Display Communities instead of Spaces", "Display Communities instead of Spaces": "Display Communities instead of Spaces",
"Temporarily show communities instead of Spaces for this session. Support for this will be removed in the near future. This will reload Element.": "Temporarily show communities instead of Spaces for this session. Support for this will be removed in the near future. This will reload Element.", "Temporarily show communities instead of Spaces for this session. Support for this will be removed in the near future. This will reload Element.": "Temporarily show communities instead of Spaces for this session. Support for this will be removed in the near future. This will reload Element.",
"Developer mode": "Developer mode", "Developer mode": "Developer mode",
"Automatically send debug logs on any error": "Automatically send debug logs on any error",
"Collecting app version information": "Collecting app version information", "Collecting app version information": "Collecting app version information",
"Collecting logs": "Collecting logs", "Collecting logs": "Collecting logs",
"Uploading logs": "Uploading logs", "Uploading logs": "Uploading logs",

View file

@ -192,6 +192,11 @@ export async function sendSentryReport(userText: string, issueUrl: string, error
} }
} }
export function setSentryUser(mxid: string): void {
if (!SdkConfig.get().sentry || !SettingsStore.getValue("automaticErrorReporting")) return;
Sentry.setUser({ username: mxid });
}
interface ISentryConfig { interface ISentryConfig {
dsn: string; dsn: string;
environment?: string; environment?: string;
@ -199,21 +204,28 @@ interface ISentryConfig {
export async function initSentry(sentryConfig: ISentryConfig): Promise<void> { export async function initSentry(sentryConfig: ISentryConfig): Promise<void> {
if (!sentryConfig) return; if (!sentryConfig) return;
// Only enable Integrations.GlobalHandlers, which hooks uncaught exceptions, if automaticErrorReporting is true
const integrations = [
new Sentry.Integrations.InboundFilters(),
new Sentry.Integrations.FunctionToString(),
new Sentry.Integrations.Breadcrumbs(),
new Sentry.Integrations.UserAgent(),
new Sentry.Integrations.Dedupe(),
];
if (SettingsStore.getValue("automaticErrorReporting")) {
integrations.push(new Sentry.Integrations.GlobalHandlers(
{ onerror: false, onunhandledrejection: true }));
integrations.push(new Sentry.Integrations.TryCatch());
}
Sentry.init({ Sentry.init({
dsn: sentryConfig.dsn, dsn: sentryConfig.dsn,
release: process.env.VERSION, release: process.env.VERSION,
environment: sentryConfig.environment, environment: sentryConfig.environment,
defaultIntegrations: false, defaultIntegrations: false,
autoSessionTracking: false, autoSessionTracking: false,
integrations: [ integrations,
// specifically disable Integrations.GlobalHandlers, which hooks uncaught exceptions - we don't
// want to capture those at this stage, just explicit rageshakes
new Sentry.Integrations.InboundFilters(),
new Sentry.Integrations.FunctionToString(),
new Sentry.Integrations.Breadcrumbs(),
new Sentry.Integrations.UserAgent(),
new Sentry.Integrations.Dedupe(),
],
// Set to 1.0 which is reasonable if we're only submitting Rageshakes; will need to be set < 1.0 // Set to 1.0 which is reasonable if we're only submitting Rageshakes; will need to be set < 1.0
// if we collect more frequently. // if we collect more frequently.
tracesSampleRate: 1.0, tracesSampleRate: 1.0,

View file

@ -762,6 +762,12 @@ export const SETTINGS: {[setting: string]: ISetting} = {
supportedLevels: LEVELS_ACCOUNT_SETTINGS, supportedLevels: LEVELS_ACCOUNT_SETTINGS,
default: false, default: false,
}, },
"automaticErrorReporting": {
displayName: _td("Automatically send debug logs on any error"),
supportedLevels: LEVELS_ACCOUNT_SETTINGS,
default: false,
controller: new ReloadOnChangeController(),
},
[UIFeature.RoomHistorySettings]: { [UIFeature.RoomHistorySettings]: {
supportedLevels: LEVELS_UI_FEATURE, supportedLevels: LEVELS_UI_FEATURE,
default: true, default: true,