diff --git a/client/package.json b/client/package.json index 21cace83c83..032cb4ab2a0 100644 --- a/client/package.json +++ b/client/package.json @@ -47,8 +47,7 @@ "@johmun/vue-tags-input": "^2.1.0", "@monaco-editor/loader": "^1.5.0", "@popperjs/core": "^2.11.8", - "@sentry/browser": "^7.74.1", - "@sentry/vue": "^7.114.0", + "@sentry/vue": "^10.45.0", "@vueuse/core": "^10.5.0", "@vueuse/math": "^10.9.0", "@vueuse/shared": "^10.5.0", diff --git a/client/pnpm-lock.yaml b/client/pnpm-lock.yaml index 2f5ed9d1ceb..aa2b1e46988 100644 --- a/client/pnpm-lock.yaml +++ b/client/pnpm-lock.yaml @@ -64,12 +64,9 @@ importers: '@popperjs/core': specifier: ^2.11.8 version: 2.11.8 - '@sentry/browser': - specifier: ^7.74.1 - version: 7.114.0 '@sentry/vue': - specifier: ^7.114.0 - version: 7.114.0(vue@2.7.16) + specifier: ^10.45.0 + version: 10.45.0(pinia@2.3.1(typescript@5.8.3)(vue@2.7.16))(vue@2.7.16) '@vueuse/core': specifier: ^10.5.0 version: 10.5.0(vue@2.7.16) @@ -1184,47 +1181,42 @@ packages: cpu: [x64] os: [win32] - '@sentry-internal/feedback@7.114.0': - resolution: {integrity: sha512-kUiLRUDZuh10QE9JbSVVLgqxFoD9eDPOzT0MmzlPuas8JlTmJuV4FtSANNcqctd5mBuLt2ebNXH0MhRMwyae4A==} - engines: {node: '>=12'} + '@sentry-internal/browser-utils@10.45.0': + resolution: {integrity: sha512-ZPZpeIarXKScvquGx2AfNKcYiVNDA4wegMmjyGVsTA2JPmP0TrJoO3UybJS6KGDeee8V3I3EfD/ruauMm7jOFQ==} + engines: {node: '>=18'} - '@sentry-internal/replay-canvas@7.114.0': - resolution: {integrity: sha512-6rTiqmKi/FYtesdM2TM2U+rh6BytdPjLP65KTUodtxohJ+r/3m+termj2o4BhIYPE1YYOZNmbZfwebkuQPmWeg==} - engines: {node: '>=12'} + '@sentry-internal/feedback@10.45.0': + resolution: {integrity: sha512-vCSurazFVq7RUeYiM5X326jA5gOVrWYD6lYX2fbjBOMcyCEhDnveNxMT62zKkZDyNT/jyD194nz/cjntBUkyWA==} + engines: {node: '>=18'} - '@sentry-internal/tracing@7.114.0': - resolution: {integrity: sha512-dOuvfJN7G+3YqLlUY4HIjyWHaRP8vbOgF+OsE5w2l7ZEn1rMAaUbPntAR8AF9GBA6j2zWNoSo8e7GjbJxVofSg==} - engines: {node: '>=8'} + '@sentry-internal/replay-canvas@10.45.0': + resolution: {integrity: sha512-nvq/AocdZTuD7y0KSiWi3gVaY0s5HOFy86mC/v1kDZmT/jsBAzN5LDkk/f1FvsWma1peqQmpUqxvhC+YIW294Q==} + engines: {node: '>=18'} - '@sentry/browser@7.114.0': - resolution: {integrity: sha512-ijJ0vOEY6U9JJADVYGkUbLrAbpGSQgA4zV+KW3tcsBLX9M1jaWq4BV1PWHdzDPPDhy4OgfOjIfaMb5BSPn1U+g==} - engines: {node: '>=8'} + '@sentry-internal/replay@10.45.0': + resolution: {integrity: sha512-vjosRoGA1bzhVAEO1oce+CsRdd70quzBeo7WvYqpcUnoLe/Rv8qpOMqWX3j26z7XfFHMExWQNQeLxmtYOArvlw==} + engines: {node: '>=18'} - '@sentry/core@7.114.0': - resolution: {integrity: sha512-YnanVlmulkjgZiVZ9BfY9k6I082n+C+LbZo52MTvx3FY6RE5iyiPMpaOh67oXEZRWcYQEGm+bKruRxLVP6RlbA==} - engines: {node: '>=8'} + '@sentry/browser@10.45.0': + resolution: {integrity: sha512-e/a8UMiQhqqv706McSIcG6XK+AoQf9INthi2pD+giZfNRTzXTdqHzUT5OIO5hg8Am6eF63nDJc+vrYNPhzs51Q==} + engines: {node: '>=18'} - '@sentry/integrations@7.114.0': - resolution: {integrity: sha512-BJIBWXGKeIH0ifd7goxOS29fBA8BkEgVVCahs6xIOXBjX1IRS6PmX0zYx/GP23nQTfhJiubv2XPzoYOlZZmDxg==} - engines: {node: '>=8'} + '@sentry/core@10.45.0': + resolution: {integrity: sha512-s69UXxvefeQxuZ5nY7/THtTrIEvJxNVCp3ns4kwoCw1qMpgpvn/296WCKVmM7MiwnaAdzEKnAvLAwaxZc2nM7Q==} + engines: {node: '>=18'} - '@sentry/replay@7.114.0': - resolution: {integrity: sha512-UvEajoLIX9n2poeW3R4Ybz7D0FgCGXoFr/x/33rdUEMIdTypknxjJWxg6fJngIduzwrlrvWpvP8QiZXczYQy2Q==} - engines: {node: '>=12'} - - '@sentry/types@7.114.0': - resolution: {integrity: sha512-tsqkkyL3eJtptmPtT0m9W/bPLkU7ILY7nvwpi1hahA5jrM7ppoU0IMaQWAgTD+U3rzFH40IdXNBFb8Gnqcva4w==} - engines: {node: '>=8'} - - '@sentry/utils@7.114.0': - resolution: {integrity: sha512-319N90McVpupQ6vws4+tfCy/03AdtsU0MurIE4+W5cubHME08HtiEWlfacvAxX+yuKFhvdsO4K4BB/dj54ideg==} - engines: {node: '>=8'} - - '@sentry/vue@7.114.0': - resolution: {integrity: sha512-tHydxMJFpUcsmNCbXo59SqeNImX+3mMO+j3T0eLsDZy1NY2fSDFP6PFvuc72RsqiwCKeCKQmQDtAVa5Vt5ZlQA==} - engines: {node: '>=8'} + '@sentry/vue@10.45.0': + resolution: {integrity: sha512-p6ghTgQtiCBZ+Yw0B2xmC69S8AdCRRsYvbTHW7MJYspwNnJDs7rqgCBqOxNhvr3tsKdDuEOEHLtf/5hbKi+8xQ==} + engines: {node: '>=18'} peerDependencies: + '@tanstack/vue-router': ^1.64.0 + pinia: 2.x || 3.x vue: 2.7.16 + peerDependenciesMeta: + '@tanstack/vue-router': + optional: true + pinia: + optional: true '@socket.io/component-emitter@3.1.2': resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==} @@ -3046,9 +3038,6 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} - lie@3.1.1: - resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} - lie@3.3.0: resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} @@ -3063,9 +3052,6 @@ packages: linkifyjs@4.3.2: resolution: {integrity: sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==} - localforage@1.10.0: - resolution: {integrity: sha512-14/H1aX7hzBBmmh7sGPd+AOMkkIrHM3Z1PAyGgZigA1H1p5O5ANnMyWzvpAETtG68/dC4pC0ncy3+PPGzXZHPg==} - locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -5188,68 +5174,41 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.53.3': optional: true - '@sentry-internal/feedback@7.114.0': + '@sentry-internal/browser-utils@10.45.0': dependencies: - '@sentry/core': 7.114.0 - '@sentry/types': 7.114.0 - '@sentry/utils': 7.114.0 + '@sentry/core': 10.45.0 - '@sentry-internal/replay-canvas@7.114.0': + '@sentry-internal/feedback@10.45.0': dependencies: - '@sentry/core': 7.114.0 - '@sentry/replay': 7.114.0 - '@sentry/types': 7.114.0 - '@sentry/utils': 7.114.0 + '@sentry/core': 10.45.0 - '@sentry-internal/tracing@7.114.0': + '@sentry-internal/replay-canvas@10.45.0': dependencies: - '@sentry/core': 7.114.0 - '@sentry/types': 7.114.0 - '@sentry/utils': 7.114.0 + '@sentry-internal/replay': 10.45.0 + '@sentry/core': 10.45.0 - '@sentry/browser@7.114.0': + '@sentry-internal/replay@10.45.0': dependencies: - '@sentry-internal/feedback': 7.114.0 - '@sentry-internal/replay-canvas': 7.114.0 - '@sentry-internal/tracing': 7.114.0 - '@sentry/core': 7.114.0 - '@sentry/integrations': 7.114.0 - '@sentry/replay': 7.114.0 - '@sentry/types': 7.114.0 - '@sentry/utils': 7.114.0 + '@sentry-internal/browser-utils': 10.45.0 + '@sentry/core': 10.45.0 - '@sentry/core@7.114.0': + '@sentry/browser@10.45.0': dependencies: - '@sentry/types': 7.114.0 - '@sentry/utils': 7.114.0 + '@sentry-internal/browser-utils': 10.45.0 + '@sentry-internal/feedback': 10.45.0 + '@sentry-internal/replay': 10.45.0 + '@sentry-internal/replay-canvas': 10.45.0 + '@sentry/core': 10.45.0 - '@sentry/integrations@7.114.0': + '@sentry/core@10.45.0': {} + + '@sentry/vue@10.45.0(pinia@2.3.1(typescript@5.8.3)(vue@2.7.16))(vue@2.7.16)': dependencies: - '@sentry/core': 7.114.0 - '@sentry/types': 7.114.0 - '@sentry/utils': 7.114.0 - localforage: 1.10.0 - - '@sentry/replay@7.114.0': - dependencies: - '@sentry-internal/tracing': 7.114.0 - '@sentry/core': 7.114.0 - '@sentry/types': 7.114.0 - '@sentry/utils': 7.114.0 - - '@sentry/types@7.114.0': {} - - '@sentry/utils@7.114.0': - dependencies: - '@sentry/types': 7.114.0 - - '@sentry/vue@7.114.0(vue@2.7.16)': - dependencies: - '@sentry/browser': 7.114.0 - '@sentry/core': 7.114.0 - '@sentry/types': 7.114.0 - '@sentry/utils': 7.114.0 + '@sentry/browser': 10.45.0 + '@sentry/core': 10.45.0 vue: 2.7.16 + optionalDependencies: + pinia: 2.3.1(typescript@5.8.3)(vue@2.7.16) '@socket.io/component-emitter@3.1.2': {} @@ -7371,10 +7330,6 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 - lie@3.1.1: - dependencies: - immediate: 3.0.6 - lie@3.3.0: dependencies: immediate: 3.0.6 @@ -7389,10 +7344,6 @@ snapshots: linkifyjs@4.3.2: {} - localforage@1.10.0: - dependencies: - lie: 3.1.1 - locate-path@6.0.0: dependencies: p-locate: 5.0.0 diff --git a/client/src/app/addons/sentry.js b/client/src/app/addons/sentry.js deleted file mode 100644 index b2d515d7c29..00000000000 --- a/client/src/app/addons/sentry.js +++ /dev/null @@ -1,43 +0,0 @@ -import * as Sentry from "@sentry/vue"; -import Vue from "vue"; - -/** - * Initializes Sentry, one of the few functions that should actually be - * in a global init. Transplanted here from js-app.mako - * - * @param {object} config Galaxy configuration object - */ -export async function initSentry(Galaxy, router) { - console.log("initSentry"); - const config = Galaxy.config; - if (config.sentry_dsn_public) { - const sentry_dsn_public = config.sentry_dsn_public; - const email = Galaxy.user.get("email"); - let release = Galaxy.config.version_major; - if (Galaxy.config.version_minor) { - release += `.${Galaxy.config.version_minor}`; - } - Sentry.init({ - Vue, - dsn: sentry_dsn_public, - integrations: [Sentry.browserTracingIntegration({ router })], - release: release, - beforeSend(event, hint) { - const error = hint.originalException; - if (["AdminRequired", "RegisteredUserRequired"].includes(error?.name)) { - // ignore these error events - return null; - } - return event; - }, - }); - if (email) { - Sentry.configureScope((scope) => { - scope.setUser({ - email: email, - }); - }); - } - Galaxy.Sentry = Sentry; - } -} diff --git a/client/src/app/addons/sentry.ts b/client/src/app/addons/sentry.ts new file mode 100644 index 00000000000..1e8aa08a942 --- /dev/null +++ b/client/src/app/addons/sentry.ts @@ -0,0 +1,83 @@ +import * as Sentry from "@sentry/vue"; +import Vue from "vue"; +import type VueRouter from "vue-router"; + +interface GalaxyConfig { + sentry_dsn_public?: string; + sentry_client_traces_sample_rate?: number; + version_major: string; + version_minor?: string; +} + +interface GalaxyUser { + get(attr: string): string | undefined; + attributes?: { + preferences?: { + extra_user_preferences?: string; + }; + }; +} + +interface GalaxyInstance { + config: GalaxyConfig; + user?: GalaxyUser; +} + +function isReplayEnabled(user?: GalaxyUser): boolean { + try { + const prefsJson = user?.attributes?.preferences?.extra_user_preferences; + if (!prefsJson) { + return false; + } + const prefs = JSON.parse(prefsJson); + const value = prefs["sentry_replay|enabled"]; + return value === true || value === "true"; + } catch { + return false; + } +} + +export function initSentry(Galaxy: GalaxyInstance, router: VueRouter): void { + const config = Galaxy.config; + if (!config.sentry_dsn_public) { + return; + } + + const email = Galaxy.user?.get("email"); + let release = config.version_major; + if (config.version_minor) { + release += `.${config.version_minor}`; + } + + const replayEnabled = isReplayEnabled(Galaxy.user); + const integrations = [Sentry.browserTracingIntegration({ router })]; + if (replayEnabled) { + integrations.push( + Sentry.replayIntegration({ + maskAllText: true, + blockAllMedia: true, + }), + ); + } + + Sentry.init({ + Vue, + dsn: config.sentry_dsn_public, + integrations, + release, + tracesSampleRate: config.sentry_client_traces_sample_rate ?? 0, + replaysSessionSampleRate: 0, + replaysOnErrorSampleRate: replayEnabled ? 1.0 : 0, + beforeSend(event, hint) { + const error = hint.originalException; + if (error instanceof Error && ["AdminRequired", "RegisteredUserRequired"].includes(error.name)) { + return null; + } + return event; + }, + }); + + if (email) { + Sentry.setUser({ email }); + } +} diff --git a/client/src/entry/analysis/index.ts b/client/src/entry/analysis/index.ts index a5bf9dab06c..29ff1bbb5db 100644 --- a/client/src/entry/analysis/index.ts +++ b/client/src/entry/analysis/index.ts @@ -21,7 +21,7 @@ window.addEventListener("load", async () => { const router = getRouter(Galaxy); // Initialize globals - await initSentry(Galaxy, router); + initSentry(Galaxy, router); await initWebhooks(Galaxy); // Mount application diff --git a/doc/source/admin/galaxy_options.rst b/doc/source/admin/galaxy_options.rst index 45261c0f195..2cff18ad9e6 100644 --- a/doc/source/admin/galaxy_options.rst +++ b/doc/source/admin/galaxy_options.rst @@ -3300,6 +3300,19 @@ :Type: float +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +``sentry_client_traces_sample_rate`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:Description: + Sample rate for client-side (browser) performance tracing, between + 0 and 1. Controls what fraction of page loads and navigations + generate performance traces sent to Sentry. Independent of + sentry_traces_sample_rate, which controls server-side tracing. +:Default: ``0.0`` +:Type: float + + ~~~~~~~~~~~~~~~~~~~ ``sentry_ca_certs`` ~~~~~~~~~~~~~~~~~~~ diff --git a/lib/galaxy/config/sample/galaxy.yml.sample b/lib/galaxy/config/sample/galaxy.yml.sample index c0cf9e47245..24c5a9238ae 100644 --- a/lib/galaxy/config/sample/galaxy.yml.sample +++ b/lib/galaxy/config/sample/galaxy.yml.sample @@ -1911,6 +1911,12 @@ galaxy: # to Sentry. A value higher than 0 is required to analyze performance. #sentry_traces_sample_rate: 0.0 + # Sample rate for client-side (browser) performance tracing, between 0 + # and 1. Controls what fraction of page loads and navigations generate + # performance traces sent to Sentry. Independent of + # sentry_traces_sample_rate, which controls server-side tracing. + #sentry_client_traces_sample_rate: 0.0 + # Use this option to provide the path to location of the CA # (Certificate Authority) certificate file if the sentry server uses a # self-signed certificate. diff --git a/lib/galaxy/config/sample/user_preferences_extra_conf.yml.sample b/lib/galaxy/config/sample/user_preferences_extra_conf.yml.sample index 69da2bcf02a..1d3486e90d5 100644 --- a/lib/galaxy/config/sample/user_preferences_extra_conf.yml.sample +++ b/lib/galaxy/config/sample/user_preferences_extra_conf.yml.sample @@ -125,7 +125,7 @@ preferences: label: Creator name to associate with new records (formatted as "Last name, First name"). If left blank "Anonymous Galaxy User" will be used. You can always change this by editing your record directly. type: text required: False - + dataverse: description: Your Dataverse Integration Settings inputs: @@ -170,6 +170,15 @@ preferences: required: False value: False + sentry_replay: + description: Session Replay for error diagnostics + inputs: + - name: enabled + label: Allow session replay recordings to be sent when errors occur. These recordings help administrators diagnose issues but may capture screen content. + type: boolean + required: False + value: False + # Used in file_sources_conf.yml elabftw: description: Your eLabFTW Integration Settings diff --git a/lib/galaxy/config/schemas/config_schema.yml b/lib/galaxy/config/schemas/config_schema.yml index d6d3e027f64..6b8a553782c 100644 --- a/lib/galaxy/config/schemas/config_schema.yml +++ b/lib/galaxy/config/schemas/config_schema.yml @@ -2431,6 +2431,16 @@ mapping: will have that percentage chance of being sent to Sentry. A value higher than 0 is required to analyze performance. + sentry_client_traces_sample_rate: + type: float + default: 0.0 + required: false + desc: | + Sample rate for client-side (browser) performance tracing, between 0 + and 1. Controls what fraction of page loads and navigations generate + performance traces sent to Sentry. Independent of + sentry_traces_sample_rate, which controls server-side tracing. + sentry_ca_certs: type: str required: False diff --git a/lib/galaxy/managers/configuration.py b/lib/galaxy/managers/configuration.py index 192c7d56725..bf49cce94d5 100644 --- a/lib/galaxy/managers/configuration.py +++ b/lib/galaxy/managers/configuration.py @@ -245,6 +245,7 @@ class ConfigSerializer(base.ModelSerializer): "install_resolver_dependencies": _use_config, "enable_tool_generated_tours": _use_config, "sentry_dsn_public": lambda item, key, **context: item.sentry_dsn_public, + "sentry_client_traces_sample_rate": _use_config, "enable_webhooks": lambda item, key, **context: hasattr(self.app, "webhooks_registry") and bool(self.app.webhooks_registry.webhooks), }