diff --git a/package.json b/package.json index 2445e3c973..beb30e231f 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,8 @@ }, "dependencies": { "@babel/runtime": "^7.12.5", + "@sentry/browser": "^6.11.0", + "@sentry/tracing": "^6.11.0", "await-lock": "^2.1.0", "blurhash": "^1.1.3", "browser-encrypt-attachment": "^0.3.0", diff --git a/src/components/structures/MatrixChat.tsx b/src/components/structures/MatrixChat.tsx index 60c78b5f9e..e288884a5a 100644 --- a/src/components/structures/MatrixChat.tsx +++ b/src/components/structures/MatrixChat.tsx @@ -108,6 +108,7 @@ import SoftLogout from './auth/SoftLogout'; import { makeRoomPermalink } from "../../utils/permalinks/Permalinks"; import { copyPlaintext } from "../../utils/strings"; import { PosthogAnalytics } from '../../PosthogAnalytics'; +import {initSentry, sendSentryReport} from "../../sentry"; /** constants for MatrixChat.state.view */ export enum Views { @@ -393,6 +394,16 @@ export default class MatrixChat extends React.PureComponent { PosthogAnalytics.instance.updatePlatformSuperProperties(); CountlyAnalytics.instance.enable(/* anonymous = */ true); + + initSentry(SdkConfig.get()["sentry"]); + setTimeout(() => { + try { + const e = new Error("whoops"); + throw(e); + } catch (e) { + sendSentryReport("user text", "label", e); + } + }, 4000); } private async postLoginSetup() { diff --git a/src/components/views/dialogs/BugReportDialog.tsx b/src/components/views/dialogs/BugReportDialog.tsx index 3df05dac6e..b12aecd95c 100644 --- a/src/components/views/dialogs/BugReportDialog.tsx +++ b/src/components/views/dialogs/BugReportDialog.tsx @@ -29,11 +29,13 @@ import BaseDialog from "./BaseDialog"; import Field from '../elements/Field'; import Spinner from "../elements/Spinner"; import DialogButtons from "../elements/DialogButtons"; +import {sendSentryReport} from "../../../sentry"; interface IProps { onFinished: (success: boolean) => void; initialText?: string; label?: string; + error?: Error; } interface IState { @@ -113,6 +115,12 @@ export default class BugReportDialog extends React.Component { }); } }); + + // Send a Sentry report if the user agreed to send logs and if there's an error object (Sentry won't be very + // useful for grouping errors without exception information to aggregate with) + if (sendLogs) { + sendSentryReport(userText, this.props.label, this.props.error); + } }; private onDownload = async (): Promise => { diff --git a/src/components/views/elements/ErrorBoundary.tsx b/src/components/views/elements/ErrorBoundary.tsx index 334e569163..a1b67cb347 100644 --- a/src/components/views/elements/ErrorBoundary.tsx +++ b/src/components/views/elements/ErrorBoundary.tsx @@ -71,6 +71,7 @@ export default class ErrorBoundary extends React.PureComponent<{}, IState> { private onBugReport = (): void => { Modal.createTrackedDialog('Bug Report Dialog', '', BugReportDialog, { label: 'react-soft-crash', + error: this.state.error }); }; diff --git a/src/rageshake/rageshake.js b/src/rageshake/rageshake.js index 9512f62e42..a3a59a472b 100644 --- a/src/rageshake/rageshake.js +++ b/src/rageshake/rageshake.js @@ -84,7 +84,7 @@ class ConsoleLogger { // In development, it can be useful to log complex cyclic // objects to the console for inspection. This is fine for // the console, but default `stringify` can't handle that. - // We workaround this by using a special replacer function + // We w orkaround this by using a special replacer function // to only log values of the root object and avoid cycles. return JSON.stringify(arg, (key, value) => { if (key && typeof value === "object") { diff --git a/src/sentry.ts b/src/sentry.ts new file mode 100644 index 0000000000..925aa48251 --- /dev/null +++ b/src/sentry.ts @@ -0,0 +1,48 @@ +import * as Sentry from "@sentry/browser"; +import { Integrations } from "@sentry/tracing"; +import PlatformPeg from "./PlatformPeg"; +import SdkConfig from "./SdkConfig"; + +export function sendSentryReport(userText: string, label: string, error: Error): void { + if (!SdkConfig.get()["sentry"]) return; + + // Ignore reports without errors, as they're not useful in sentry and can't easily be aggregated + if (error) { + Sentry.captureException(error); + } +} + +interface ISentryConfig { + dsn: string; + environment?: string; +} + +export async function initSentry(sentryConfig: ISentryConfig): Promise { + if (!sentryConfig) return; + const platform = PlatformPeg.get(); + let appVersion = "unknown"; + try { + appVersion = await platform.getAppVersion(); + } catch (e) {} + + Sentry.init({ + dsn: sentryConfig.dsn, + release: `${platform.getHumanReadableName()}@${appVersion}`, + environment: sentryConfig.environment, + defaultIntegrations: false, + autoSessionTracking: false, + debug: true, + 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 + // if we collect more frequently. + tracesSampleRate: 1.0, + }); +} diff --git a/yarn.lock b/yarn.lock index a780d1ffa0..d9896fe56a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1453,11 +1453,74 @@ tslib "^2.2.0" webcrypto-core "^1.2.0" +"@sentry/browser@^6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-6.11.0.tgz#9e90bbc0488ebcdd1e67937d8d5b4f13c3f6dee0" + integrity sha512-Qr2QRA0t5/S9QQqxzYKvM9W8prvmiWuldfwRX4hubovXzcXLgUi4WK0/H612wSbYZ4dNAEcQbtlxFWJNN4wxdg== + dependencies: + "@sentry/core" "6.11.0" + "@sentry/types" "6.11.0" + "@sentry/utils" "6.11.0" + tslib "^1.9.3" + +"@sentry/core@6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.11.0.tgz#40e94043afcf6407a109be26655c77832c64e740" + integrity sha512-09TB+f3pqEq8LFahFWHO6I/4DxHo+NcS52OkbWMDqEi6oNZRD7PhPn3i14LfjsYVv3u3AESU8oxSEGbFrr2UjQ== + dependencies: + "@sentry/hub" "6.11.0" + "@sentry/minimal" "6.11.0" + "@sentry/types" "6.11.0" + "@sentry/utils" "6.11.0" + tslib "^1.9.3" + +"@sentry/hub@6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.11.0.tgz#ddf9ddb0577d1c8290dc02c0242d274fe84d6c16" + integrity sha512-pT9hf+ZJfVFpoZopoC+yJmFNclr4NPqPcl2cgguqCHb69DklD1NxgBNWK8D6X05qjnNFDF991U6t1mxP9HrGuw== + dependencies: + "@sentry/types" "6.11.0" + "@sentry/utils" "6.11.0" + tslib "^1.9.3" + +"@sentry/minimal@6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-6.11.0.tgz#806d5512658370e40827b3e3663061db708fff33" + integrity sha512-XkZ7qrdlGp4IM/gjGxf1Q575yIbl5RvPbg+WFeekpo16Ufvzx37Mr8c2xsZaWosISVyE6eyFpooORjUlzy8EDw== + dependencies: + "@sentry/hub" "6.11.0" + "@sentry/types" "6.11.0" + tslib "^1.9.3" + +"@sentry/tracing@^6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@sentry/tracing/-/tracing-6.11.0.tgz#9bd9287addea1ebc12c75b226f71c7713c0fac4f" + integrity sha512-9VA1/SY++WeoMQI4K6n/sYgIdRtCu9NLWqmGqu/5kbOtESYFgAt1DqSyqGCr00ZjQiC2s7tkDkTNZb38K6KytQ== + dependencies: + "@sentry/hub" "6.11.0" + "@sentry/minimal" "6.11.0" + "@sentry/types" "6.11.0" + "@sentry/utils" "6.11.0" + tslib "^1.9.3" + +"@sentry/types@6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.11.0.tgz#5122685478d32ddacd3a891cbcf550012df85f7c" + integrity sha512-gm5H9eZhL6bsIy/h3T+/Fzzz2vINhHhqd92CjHle3w7uXdTdFV98i2pDpErBGNTSNzbntqOMifYEB5ENtZAvcg== + "@sentry/types@^6.10.0": version "6.10.0" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.10.0.tgz#6b1f44e5ed4dbc2710bead24d1b32fb08daf04e1" integrity sha512-M7s0JFgG7/6/yNVYoPUbxzaXDhnzyIQYRRJJKRaTD77YO4MHvi4Ke8alBWqD5fer0cPIfcSkBqa9BLdqRqcMWw== +"@sentry/utils@6.11.0": + version "6.11.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.11.0.tgz#d1dee4faf4d9c42c54bba88d5a66fb96b902a14c" + integrity sha512-IOvyFHcnbRQxa++jO+ZUzRvFHEJ1cZjrBIQaNVc0IYF0twUOB5PTP6joTcix38ldaLeapaPZ9LGfudbvYvxkdg== + dependencies: + "@sentry/types" "6.11.0" + tslib "^1.9.3" + "@sinonjs/commons@^1.7.0": version "1.8.3" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.3.tgz#3802ddd21a50a949b6721ddd72da36e67e7f1b2d"