Sentry React integration (#8086)

- Closes https://github.com/enso-org/cloud-v2/issues/720
- Integrate Sentry with React and React Router.

# Important Notes
This is currently BROKEN as it requires the Sentry DSN, otherwise no requests are part of a transaction. Not sure how feasible this is unfortunately, especially as (I'm assuming) it will be different for each backend...
... I guess worst case it can be configured via an environment variable like some other build-time defines.

Also the sampling rates should be checked.
This commit is contained in:
somebody1234 2023-11-03 10:38:30 +10:00 committed by GitHub
parent 00341cd89b
commit 8e20fc66dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 148 additions and 7 deletions

View File

@ -17,6 +17,7 @@
}, },
"dependencies": { "dependencies": {
"@heroicons/react": "^2.0.15", "@heroicons/react": "^2.0.15",
"@sentry/react": "^7.74.0",
"@types/node": "^18.17.5", "@types/node": "^18.17.5",
"@types/react": "^18.0.27", "@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10", "@types/react-dom": "^18.0.10",

View File

@ -7,6 +7,8 @@ import * as React from 'react'
import * as router from 'react-router-dom' import * as router from 'react-router-dom'
import * as toast from 'react-toastify' import * as toast from 'react-toastify'
import * as sentry from '@sentry/react'
import * as app from '../../components/app' import * as app from '../../components/app'
import type * as authServiceModule from '../service' import type * as authServiceModule from '../service'
import * as backendModule from '../../dashboard/backend' import * as backendModule from '../../dashboard/backend'
@ -201,6 +203,7 @@ export function AuthProvider(props: AuthProviderProps) {
const goOfflineInternal = React.useCallback(() => { const goOfflineInternal = React.useCallback(() => {
setInitialized(true) setInitialized(true)
sentry.setUser(null)
setUserSession(OFFLINE_USER_SESSION) setUserSession(OFFLINE_USER_SESSION)
if (supportsLocalBackend) { if (supportsLocalBackend) {
setBackendWithoutSavingType(new localBackend.LocalBackend(projectManagerUrl)) setBackendWithoutSavingType(new localBackend.LocalBackend(projectManagerUrl))
@ -253,6 +256,7 @@ export function AuthProvider(props: AuthProviderProps) {
} else if (session == null) { } else if (session == null) {
setInitialized(true) setInitialized(true)
if (!initialized) { if (!initialized) {
sentry.setUser(null)
setUserSession(null) setUserSession(null)
} }
} else { } else {
@ -307,11 +311,19 @@ export function AuthProvider(props: AuthProviderProps) {
} }
let newUserSession: UserSession let newUserSession: UserSession
if (organization == null) { if (organization == null) {
sentry.setUser({ email: session.email })
newUserSession = { newUserSession = {
type: UserSessionType.partial, type: UserSessionType.partial,
...session, ...session,
} }
} else { } else {
sentry.setUser({
id: organization.id,
email: organization.email,
username: organization.name,
// eslint-disable-next-line @typescript-eslint/naming-convention
ip_address: '{{auto}}',
})
newUserSession = { newUserSession = {
type: UserSessionType.full, type: UserSessionType.full,
...session, ...session,
@ -493,6 +505,7 @@ export function AuthProvider(props: AuthProviderProps) {
const signOut = async () => { const signOut = async () => {
deinitializeSession() deinitializeSession()
setInitialized(false) setInitialized(false)
sentry.setUser(null)
setUserSession(null) setUserSession(null)
localStorage.clearUserSpecificEntries() localStorage.clearUserSpecificEntries()
// This should not omit success and error toasts as it is not possible // This should not omit success and error toasts as it is not possible

View File

@ -3,10 +3,14 @@
* This module declares the main DOM structure for the authentication/dashboard app. */ * This module declares the main DOM structure for the authentication/dashboard app. */
import * as React from 'react' import * as React from 'react'
import * as reactDOM from 'react-dom/client' import * as reactDOM from 'react-dom/client'
import * as reactRouter from 'react-router-dom'
import * as sentry from '@sentry/react'
import * as detect from 'enso-common/src/detect' import * as detect from 'enso-common/src/detect'
import type * as app from './components/app' import type * as app from './components/app'
import * as config from './config'
import App from './components/app' import App from './components/app'
// ================= // =================
@ -15,6 +19,8 @@ import App from './components/app'
/** The `id` attribute of the root element that the app will be rendered into. */ /** The `id` attribute of the root element that the app will be rendered into. */
const ROOT_ELEMENT_ID = 'enso-dashboard' const ROOT_ELEMENT_ID = 'enso-dashboard'
/** The fraction of non-erroring interactions that should be sampled by Sentry. */
const SENTRY_SAMPLE_RATE = 0.005
// =========== // ===========
// === run === // === run ===
@ -31,6 +37,26 @@ export // This export declaration must be broken up to satisfy the `require-jsdo
function run(props: app.AppProps) { function run(props: app.AppProps) {
const { logger, supportsDeepLinks } = props const { logger, supportsDeepLinks } = props
logger.log('Starting authentication/dashboard UI.') logger.log('Starting authentication/dashboard UI.')
sentry.init({
dsn: 'https://0dc7cb80371f466ab88ed01739a7822f@o4504446218338304.ingest.sentry.io/4506070404300800',
environment: config.ENVIRONMENT,
integrations: [
new sentry.BrowserTracing({
routingInstrumentation: sentry.reactRouterV6Instrumentation(
React.useEffect,
reactRouter.useLocation,
reactRouter.useNavigationType,
reactRouter.createRoutesFromChildren,
reactRouter.matchRoutes
),
}),
new sentry.Replay(),
],
tracesSampleRate: SENTRY_SAMPLE_RATE,
tracePropagationTargets: [config.ACTIVE_CONFIG.apiUrl.split('//')[1] ?? ''],
replaysSessionSampleRate: SENTRY_SAMPLE_RATE,
replaysOnErrorSampleRate: 1.0,
})
/** The root element into which the authentication/dashboard app will be rendered. */ /** The root element into which the authentication/dashboard app will be rendered. */
const root = document.getElementById(ROOT_ELEMENT_ID) const root = document.getElementById(ROOT_ELEMENT_ID)
if (root == null) { if (root == null) {
@ -40,13 +66,15 @@ function run(props: app.AppProps) {
// via the browser. // via the browser.
const actuallySupportsDeepLinks = supportsDeepLinks && detect.isOnElectron() const actuallySupportsDeepLinks = supportsDeepLinks && detect.isOnElectron()
reactDOM.createRoot(root).render( reactDOM.createRoot(root).render(
detect.IS_DEV_MODE ? ( <sentry.ErrorBoundary>
<React.StrictMode> {detect.IS_DEV_MODE ? (
<App {...props} /> <React.StrictMode>
</React.StrictMode> <App {...props} />
) : ( </React.StrictMode>
<App {...props} supportsDeepLinks={actuallySupportsDeepLinks} /> ) : (
) <App {...props} supportsDeepLinks={actuallySupportsDeepLinks} />
)}
</sentry.ErrorBoundary>
) )
} }
} }

99
package-lock.json generated
View File

@ -237,6 +237,7 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@heroicons/react": "^2.0.15", "@heroicons/react": "^2.0.15",
"@sentry/react": "^7.74.0",
"@types/node": "^18.17.5", "@types/node": "^18.17.5",
"@types/react": "^18.0.27", "@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10", "@types/react-dom": "^18.0.10",
@ -3261,6 +3262,96 @@
"npm": ">=7.0.0" "npm": ">=7.0.0"
} }
}, },
"node_modules/@sentry-internal/tracing": {
"version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry-internal/tracing/-/tracing-7.77.0.tgz",
"integrity": "sha512-8HRF1rdqWwtINqGEdx8Iqs9UOP/n8E0vXUu3Nmbqj4p5sQPA7vvCfq+4Y4rTqZFc7sNdFpDsRION5iQEh8zfZw==",
"dependencies": {
"@sentry/core": "7.77.0",
"@sentry/types": "7.77.0",
"@sentry/utils": "7.77.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/browser": {
"version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-7.77.0.tgz",
"integrity": "sha512-nJ2KDZD90H8jcPx9BysQLiQW+w7k7kISCWeRjrEMJzjtge32dmHA8G4stlUTRIQugy5F+73cOayWShceFP7QJQ==",
"dependencies": {
"@sentry-internal/tracing": "7.77.0",
"@sentry/core": "7.77.0",
"@sentry/replay": "7.77.0",
"@sentry/types": "7.77.0",
"@sentry/utils": "7.77.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/core": {
"version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-7.77.0.tgz",
"integrity": "sha512-Tj8oTYFZ/ZD+xW8IGIsU6gcFXD/gfE+FUxUaeSosd9KHwBQNOLhZSsYo/tTVf/rnQI/dQnsd4onPZLiL+27aTg==",
"dependencies": {
"@sentry/types": "7.77.0",
"@sentry/utils": "7.77.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/react": {
"version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry/react/-/react-7.77.0.tgz",
"integrity": "sha512-Q+htKzib5em0MdaQZMmPomaswaU3xhcVqmLi2CxqQypSjbYgBPPd+DuhrXKoWYLDDkkbY2uyfe4Lp3yLRWeXYw==",
"dependencies": {
"@sentry/browser": "7.77.0",
"@sentry/types": "7.77.0",
"@sentry/utils": "7.77.0",
"hoist-non-react-statics": "^3.3.2"
},
"engines": {
"node": ">=8"
},
"peerDependencies": {
"react": "15.x || 16.x || 17.x || 18.x"
}
},
"node_modules/@sentry/replay": {
"version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry/replay/-/replay-7.77.0.tgz",
"integrity": "sha512-M9Ik2J5ekl+C1Och3wzLRZVaRGK33BlnBwfwf3qKjgLDwfKW+1YkwDfTHbc2b74RowkJbOVNcp4m8ptlehlSaQ==",
"dependencies": {
"@sentry-internal/tracing": "7.77.0",
"@sentry/core": "7.77.0",
"@sentry/types": "7.77.0",
"@sentry/utils": "7.77.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@sentry/types": {
"version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-7.77.0.tgz",
"integrity": "sha512-nfb00XRJVi0QpDHg+JkqrmEBHsqBnxJu191Ded+Cs1OJ5oPXEW6F59LVcBScGvMqe+WEk1a73eH8XezwfgrTsA==",
"engines": {
"node": ">=8"
}
},
"node_modules/@sentry/utils": {
"version": "7.77.0",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-7.77.0.tgz",
"integrity": "sha512-NmM2kDOqVchrey3N5WSzdQoCsyDkQkiRxExPaNI2oKQ/jMWHs9yt0tSy7otPBcXs0AP59ihl75Bvm1tDRcsp5g==",
"dependencies": {
"@sentry/types": "7.77.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@sinclair/typebox": { "node_modules/@sinclair/typebox": {
"version": "0.27.8", "version": "0.27.8",
"dev": true, "dev": true,
@ -9713,6 +9804,14 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/hosted-git-info": { "node_modules/hosted-git-info": {
"version": "2.8.9", "version": "2.8.9",
"dev": true, "dev": true,