From a4e034e312827ace046d5f53ce5107aaf403864e Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Wed, 15 Jan 2025 13:01:45 +0100 Subject: [PATCH 1/6] chore(deps): add @sentry/browser --- package.json | 1 + yarn.lock | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/package.json b/package.json index bbb04a3a7f72..37644271817c 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "@mdn/browser-compat-data": "^5.6.29", "@mdn/rari": "^0.1.17", "@mozilla/glean": "5.0.3", + "@sentry/browser": "^8.49.0", "@sentry/node": "^8.49.0", "@stripe/stripe-js": "^5.5.0", "@use-it/interval": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index 04373dd0fc44..1d6bf349011f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2731,6 +2731,47 @@ resolved "https://registry.yarnpkg.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz#60de891bb126abfdc5410fdc6166aca065f10a0c" integrity sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg== +"@sentry-internal/browser-utils@8.49.0": + version "8.49.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.49.0.tgz#ce448e1b60ff75e009db70d76de9b1186ad98c60" + integrity sha512-XkPHHdFqsN7EPaB+QGUOEmpFqXiqP67t2rRZ1HG1UwJoe0PhJEKNy7b4+WRwmT7ODSt+PvFk1gNBlJBpThwH7Q== + dependencies: + "@sentry/core" "8.49.0" + +"@sentry-internal/feedback@8.49.0": + version "8.49.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.49.0.tgz#577d713ae80ba0698e67616d46050e884d143e72" + integrity sha512-v/wf7WvPxEvZUB7xrCnecI3fhevVo84hw8WlxgZIz6mLUHXEIX8xYWc9H8Yet/KKJ2uEB8GQ8aDsY6S1hVEIUA== + dependencies: + "@sentry/core" "8.49.0" + +"@sentry-internal/replay-canvas@8.49.0": + version "8.49.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.49.0.tgz#7dede7d04260f38af5e33dbc57d5412afa9f6676" + integrity sha512-/yXxI7f+Wu24FIYoRE7A0AidNxORuhAyPzb5ey1wFqMXP72nG8dXhOpcl0w+bi554FkqkLjdeUDhSOBWYZXH9g== + dependencies: + "@sentry-internal/replay" "8.49.0" + "@sentry/core" "8.49.0" + +"@sentry-internal/replay@8.49.0": + version "8.49.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.49.0.tgz#a04ca50a402407f48ca8524a44f185ad7f003754" + integrity sha512-BDiiCBxskkktTd6FNplBc9V8l14R4T/AwRIZj2itX4xnuHewTTDjVbeyvGol4roA4r+V0Mzoi31hLEGI6yFQ5Q== + dependencies: + "@sentry-internal/browser-utils" "8.49.0" + "@sentry/core" "8.49.0" + +"@sentry/browser@^8.49.0": + version "8.49.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.49.0.tgz#4dc1b5501bc398dc801651aa8e5bba452ab551a4" + integrity sha512-dS4Sw2h8EixHeXOIR++XEVMTen6xCGcIQ/XhJbsjqvddXeIijW0WkxSeTfPkfs0dsqFHSisWmlmo0xhHbXvEsQ== + dependencies: + "@sentry-internal/browser-utils" "8.49.0" + "@sentry-internal/feedback" "8.49.0" + "@sentry-internal/replay" "8.49.0" + "@sentry-internal/replay-canvas" "8.49.0" + "@sentry/core" "8.49.0" + "@sentry/core@8.49.0": version "8.49.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.49.0.tgz#b7a29989e49541b15dd537cb43940a9415f7e128" From 5056ca4ba848811da84dca6e71fab692e8114c12 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Wed, 15 Jan 2025 13:02:41 +0100 Subject: [PATCH 2/6] feat(telemetry): capture client errors with Sentry --- client/src/env.ts | 5 ++++ client/src/index.tsx | 2 ++ client/src/telemetry/sentry.ts | 55 ++++++++++++++++++++++++++++++++++ docs/envvars.md | 19 ++++++++++++ libs/constants/index.js | 1 + 5 files changed, 82 insertions(+) create mode 100644 client/src/telemetry/sentry.ts diff --git a/client/src/env.ts b/client/src/env.ts index 5f9eea8c8fb0..0b3722d8dc57 100644 --- a/client/src/env.ts +++ b/client/src/env.ts @@ -103,6 +103,11 @@ export const GLEAN_LOG_CLICK = Boolean( JSON.parse(process.env.REACT_APP_GLEAN_LOG_CLICK || "false") ); +export const SENTRY_DSN = process.env.REACT_APP_SENTRY_DSN || ""; +export const SENTRY_ENVIRONMENT = + process.env.REACT_APP_SENTRY_ENVIRONMENT || "dev"; +export const SENTRY_RELEASE = process.env.REACT_APP_SENTRY_RELEASE || "dev"; + export const AI_FEEDBACK_GITHUB_REPO = process.env.REACT_APP_AI_FEEDBACK_GITHUB_REPO || "mdn/private-ai-feedback"; diff --git a/client/src/index.tsx b/client/src/index.tsx index b9cd51c2df86..3c875cb5f407 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -8,8 +8,10 @@ import { UserDataProvider } from "./user-context"; import { UIProvider } from "./ui-context"; import { GleanProvider } from "./telemetry/glean-context"; import { PlacementProvider } from "./placement-context"; +import { initSentry } from "./telemetry/sentry"; // import * as serviceWorker from './serviceWorker'; +initSentry(); const container = document.getElementById("root"); if (!container) { diff --git a/client/src/telemetry/sentry.ts b/client/src/telemetry/sentry.ts new file mode 100644 index 000000000000..c20b8635e9cc --- /dev/null +++ b/client/src/telemetry/sentry.ts @@ -0,0 +1,55 @@ +import { SENTRY_DSN, SENTRY_ENVIRONMENT, SENTRY_RELEASE } from "../env"; + +let sentryPromise: Promise | null = null; + +/** + * Loads the Sentry module asynchronously and initializes it. + * Utilizes dynamic import to split Sentry related code into a separate chunk. + * Ensures Sentry is only loaded and initialized once. + * + * @returns A promise resolving to the initialized Sentry object. + */ +function loadSentry(): Promise { + if (!sentryPromise) { + sentryPromise = import( + /* webpackChunkName: "sentry" */ "@sentry/browser" + ).then((Sentry) => { + Sentry.init({ + dsn: SENTRY_DSN, + release: SENTRY_RELEASE || "dev", + environment: SENTRY_ENVIRONMENT || "dev", + }); + return Sentry; + }); + } + + return sentryPromise; +} + +export function initSentry() { + if (!SENTRY_DSN) { + return; + } + + let onNextError: (() => void) | null = null; + const capturedMessages = new Set(); + + const handleError = (event: ErrorEvent) => { + loadSentry().then((Sentry) => { + if (onNextError) { + onNextError(); + onNextError = null; + } + if (!capturedMessages.has(event.message)) { + // Capture every error only once. + Sentry.captureException(event); + capturedMessages.add(event.message); + } + }); + }; + + // To avoid capturing too many events, we stop listening after the first error. + onNextError = () => window.removeEventListener("error", handleError); + + window.addEventListener("error", handleError); +} diff --git a/docs/envvars.md b/docs/envvars.md index 2f600f9bec9e..b10018a00e8b 100644 --- a/docs/envvars.md +++ b/docs/envvars.md @@ -286,6 +286,25 @@ for content editing as authentication is then not required. Enables features or setup which only make sense in local development. +### `REACT_APP_SENTRY_DSN` + +**Default: `""`** + +Enables client-side error tracking with Sentry. + +### `REACT_APP_SENTRY_ENVIRONMENT` + +**Default: `"dev"`** + +Specifies the name of the current environment. + +### `REACT_APP_SENTRY_RELEASE` + +**Default: `"dev"`** + +Specifies the version of the current build. This is set to the commit hash in +deployments. + ### `REACT_APP_WRITER_MODE` **Default: `false`** diff --git a/libs/constants/index.js b/libs/constants/index.js index 7dda3d74fac1..e95103d84933 100644 --- a/libs/constants/index.js +++ b/libs/constants/index.js @@ -119,6 +119,7 @@ export const CSP_DIRECTIVES = { "https://api.github.com/search/issues", "stats.g.doubleclick.net", + "*.sentry.io", "https://api.stripe.com", ], "font-src": ["'self'"], From 3b99deaacb4bbb3951a20b6ca17652c2bd9b76f7 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Wed, 15 Jan 2025 13:03:03 +0100 Subject: [PATCH 3/6] chore(webpack): treeshake sentry --- client/config/webpack.config.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/client/config/webpack.config.js b/client/config/webpack.config.js index f6d1b7375c4a..9df7f6f5289a 100644 --- a/client/config/webpack.config.js +++ b/client/config/webpack.config.js @@ -433,6 +433,14 @@ function config(webpackEnv) { // during a production build. // Otherwise React will be compiled in the very slow development mode. new webpack.DefinePlugin(env.stringified), + // Treeshake Sentry (saves about 12 kB on the chunk). + new webpack.DefinePlugin({ + __SENTRY_DEBUG__: false, + __SENTRY_TRACING__: false, + __RRWEB_EXCLUDE_IFRAME__: true, + __RRWEB_EXCLUDE_SHADOW_DOM__: true, + __SENTRY_EXCLUDE_REPLAY_WORKER__: true, + }), // Experimental hot reloading for React . // https://github.com/facebook/react/tree/main/packages/react-refresh isEnvDevelopment && From a485704a51a524cf943635ac58525f4882299732 Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Wed, 15 Jan 2025 13:03:23 +0100 Subject: [PATCH 4/6] build(workflows): enable Sentry on prod/stage/test --- .github/workflows/prod-build.yml | 3 +++ .github/workflows/stage-build.yml | 3 +++ .github/workflows/test-build.yml | 6 ++++++ 3 files changed, 12 insertions(+) diff --git a/.github/workflows/prod-build.yml b/.github/workflows/prod-build.yml index 0413b299097e..9c24ab12b745 100644 --- a/.github/workflows/prod-build.yml +++ b/.github/workflows/prod-build.yml @@ -285,6 +285,9 @@ jobs: REACT_APP_OBSERVATORY_API_URL: https://observatory-api.mdn.mozilla.net # Sentry. + REACT_APP_SENTRY_DSN: ${{ secrets.SENTRY_DSN_CLIENT }} + REACT_APP_SENTRY_ENVIRONMENT: prod + REACT_APP_SENTRY_RELEASE: ${{ github.sha }} SENTRY_DSN_BUILD: ${{ secrets.SENTRY_DSN_BUILD }} SENTRY_ENVIRONMENT: prod SENTRY_RELEASE: ${{ github.sha }} diff --git a/.github/workflows/stage-build.yml b/.github/workflows/stage-build.yml index 57561d91e1b8..6b4a7660e84d 100644 --- a/.github/workflows/stage-build.yml +++ b/.github/workflows/stage-build.yml @@ -302,6 +302,9 @@ jobs: REACT_APP_OBSERVATORY_API_URL: https://observatory-api.mdn.allizom.net # Sentry. + REACT_APP_SENTRY_DSN: ${{ secrets.SENTRY_DSN_CLIENT }} + REACT_APP_SENTRY_ENVIRONMENT: stage + REACT_APP_SENTRY_RELEASE: ${{ github.sha }} SENTRY_DSN_BUILD: ${{ secrets.SENTRY_DSN_BUILD }} SENTRY_ENVIRONMENT: stage SENTRY_RELEASE: ${{ github.sha }} diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 8698df15a2dd..a2b34660bca3 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -238,6 +238,12 @@ jobs: # Observatory REACT_APP_OBSERVATORY_API_URL: https://observatory-api.mdn.allizom.net + + # Sentry. + REACT_APP_SENTRY_DSN: ${{ secrets.SENTRY_DSN_CLIENT }} + REACT_APP_SENTRY_ENVIRONMENT: test + REACT_APP_SENTRY_RELEASE: ${{ github.sha }} + run: | set -eo pipefail From 959ee2fb2565cda941303ad6174a547eb04462ab Mon Sep 17 00:00:00 2001 From: Claas Augner Date: Wed, 15 Jan 2025 13:31:15 +0100 Subject: [PATCH 5/6] fixup! feat(telemetry): capture client errors with Sentry --- client/src/telemetry/sentry.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/client/src/telemetry/sentry.ts b/client/src/telemetry/sentry.ts index c20b8635e9cc..14f7c2743630 100644 --- a/client/src/telemetry/sentry.ts +++ b/client/src/telemetry/sentry.ts @@ -31,14 +31,14 @@ export function initSentry() { return; } - let onNextError: (() => void) | null = null; + let onSentryLoad: (() => void) | null = null; const capturedMessages = new Set(); const handleError = (event: ErrorEvent) => { loadSentry().then((Sentry) => { - if (onNextError) { - onNextError(); - onNextError = null; + if (onSentryLoad) { + onSentryLoad(); + onSentryLoad = null; } if (!capturedMessages.has(event.message)) { // Capture every error only once. @@ -48,8 +48,8 @@ export function initSentry() { }); }; - // To avoid capturing too many events, we stop listening after the first error. - onNextError = () => window.removeEventListener("error", handleError); + // Avoid capturing too many events by stop listening when Sentry is loaded. + onSentryLoad = () => window.removeEventListener("error", handleError); window.addEventListener("error", handleError); } From 1e7cd3c1f4cf9e21ee06d5be04b385a9ff2bfdee Mon Sep 17 00:00:00 2001 From: Andi Pieper Date: Tue, 28 Jan 2025 15:31:35 +0100 Subject: [PATCH 6/6] updated @sentry/browser dependency --- package.json | 2 +- yarn.lock | 80 ++++++++++++++++++++++++++-------------------------- 2 files changed, 41 insertions(+), 41 deletions(-) diff --git a/package.json b/package.json index b58937eefdde..d765aefbf72f 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "@mdn/browser-compat-data": "^5.6.33", "@mdn/rari": "^0.1.20", "@mozilla/glean": "5.0.3", - "@sentry/browser": "^8.49.0", + "@sentry/browser": "^8.51.0", "@sentry/node": "^8.51.0", "@stripe/stripe-js": "^5.5.0", "@use-it/interval": "^1.0.0", diff --git a/yarn.lock b/yarn.lock index f1f173d7e6a3..8011062334a8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2725,46 +2725,46 @@ resolved "https://registry.yarnpkg.com/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz#60de891bb126abfdc5410fdc6166aca065f10a0c" integrity sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg== -"@sentry-internal/browser-utils@8.49.0": - version "8.49.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.49.0.tgz#ce448e1b60ff75e009db70d76de9b1186ad98c60" - integrity sha512-XkPHHdFqsN7EPaB+QGUOEmpFqXiqP67t2rRZ1HG1UwJoe0PhJEKNy7b4+WRwmT7ODSt+PvFk1gNBlJBpThwH7Q== - dependencies: - "@sentry/core" "8.49.0" - -"@sentry-internal/feedback@8.49.0": - version "8.49.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.49.0.tgz#577d713ae80ba0698e67616d46050e884d143e72" - integrity sha512-v/wf7WvPxEvZUB7xrCnecI3fhevVo84hw8WlxgZIz6mLUHXEIX8xYWc9H8Yet/KKJ2uEB8GQ8aDsY6S1hVEIUA== - dependencies: - "@sentry/core" "8.49.0" - -"@sentry-internal/replay-canvas@8.49.0": - version "8.49.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.49.0.tgz#7dede7d04260f38af5e33dbc57d5412afa9f6676" - integrity sha512-/yXxI7f+Wu24FIYoRE7A0AidNxORuhAyPzb5ey1wFqMXP72nG8dXhOpcl0w+bi554FkqkLjdeUDhSOBWYZXH9g== - dependencies: - "@sentry-internal/replay" "8.49.0" - "@sentry/core" "8.49.0" - -"@sentry-internal/replay@8.49.0": - version "8.49.0" - resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.49.0.tgz#a04ca50a402407f48ca8524a44f185ad7f003754" - integrity sha512-BDiiCBxskkktTd6FNplBc9V8l14R4T/AwRIZj2itX4xnuHewTTDjVbeyvGol4roA4r+V0Mzoi31hLEGI6yFQ5Q== - dependencies: - "@sentry-internal/browser-utils" "8.49.0" - "@sentry/core" "8.49.0" - -"@sentry/browser@^8.49.0": - version "8.49.0" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.49.0.tgz#4dc1b5501bc398dc801651aa8e5bba452ab551a4" - integrity sha512-dS4Sw2h8EixHeXOIR++XEVMTen6xCGcIQ/XhJbsjqvddXeIijW0WkxSeTfPkfs0dsqFHSisWmlmo0xhHbXvEsQ== - dependencies: - "@sentry-internal/browser-utils" "8.49.0" - "@sentry-internal/feedback" "8.49.0" - "@sentry-internal/replay" "8.49.0" - "@sentry-internal/replay-canvas" "8.49.0" - "@sentry/core" "8.49.0" +"@sentry-internal/browser-utils@8.51.0": + version "8.51.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/browser-utils/-/browser-utils-8.51.0.tgz#eaa245aefad8b3d893516ffe9535b1907f049094" + integrity sha512-r94yfRK17zNJER0hgQE4qOSy5pWzsnFcGTJQSqhSEKUcC4KK37qSfoPrPejFxtIqXhqlkd/dTWKvrMwXWcn0MQ== + dependencies: + "@sentry/core" "8.51.0" + +"@sentry-internal/feedback@8.51.0": + version "8.51.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/feedback/-/feedback-8.51.0.tgz#58e1de053b175e6fe5896e9b1282d754ea4e12d5" + integrity sha512-VgfxSZWLYUPKDnkt2zG+Oe5ccv8U3WPM6Mo4kfABIJT3Ai4VbZB7+vb2a4pm6lUCF9DeOPXHb5o9Tg17SHDAHw== + dependencies: + "@sentry/core" "8.51.0" + +"@sentry-internal/replay-canvas@8.51.0": + version "8.51.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay-canvas/-/replay-canvas-8.51.0.tgz#c896448d456290f8f4eb99df924d1bcc07908947" + integrity sha512-ERXIbwdULkdtIQnfkMLRVfpoGV2rClwySGRlTPepFKeLxlcXo9o09cPu+qbukiDnGK0cgEgRnrV961hMg21Bmw== + dependencies: + "@sentry-internal/replay" "8.51.0" + "@sentry/core" "8.51.0" + +"@sentry-internal/replay@8.51.0": + version "8.51.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/replay/-/replay-8.51.0.tgz#c121518ef493afcf38c900d37e12af68787351d5" + integrity sha512-lkm7id3a2n3yMZeF5socCVQUeEeShNOGr7Wtsmb5RORacEnld0z+NfbMTilo1mDwiWBzI5OYBjm62eglm1HFsQ== + dependencies: + "@sentry-internal/browser-utils" "8.51.0" + "@sentry/core" "8.51.0" + +"@sentry/browser@^8.51.0": + version "8.51.0" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-8.51.0.tgz#1d208785a432f4a4aee616da459e118994c8661f" + integrity sha512-1kbbyVfBBAx5Xyynp+lC5lLnAHo0qJ2r4mtmdT6koPjesvoOocEK0QQnouQBmdUbm3L0L/bPI1SgXjbeJyhzHQ== + dependencies: + "@sentry-internal/browser-utils" "8.51.0" + "@sentry-internal/feedback" "8.51.0" + "@sentry-internal/replay" "8.51.0" + "@sentry-internal/replay-canvas" "8.51.0" + "@sentry/core" "8.51.0" "@sentry/core@8.51.0": version "8.51.0"