From 1f101a2832f432f408e59357bf6057453626f7dc Mon Sep 17 00:00:00 2001 From: Avinash Chowdhury Date: Fri, 13 Dec 2024 00:37:39 +0530 Subject: [PATCH] Upgrade from raven-js to sentry/browser (#2509) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Which problem is this PR solving? - Resolves #2412 - Replaced and closes #2417 ## Description of the changes This PR is a continuation of the migration from `raven-js` to `@sentry/browser`. - The final step involves testing the implementation with a real Google Analytics account. - The google tags are displayed correctly in [Tag Hound](https://chromewebstore.google.com/detail/taghound-analyticsgtmpixe/canpneabbfipaelecfibpmmjbdkiaolf?hl=en) chrome extension. - Here is the screenshot of logging tag managers details and event data. ![Events from TagHound](https://i.imgur.com/1fyHg2n.png) ## How was this change tested? - Placed the GA Measurement ID in `default-config.tsx`. - Varified navigation events using TagHound. Screenshots of the events been tracked are attached - **Note**: Ensure GTM tracking is enabled in TagHound, otherwise the Google tags will not be tracked. - This test works in the local environment, but I am unable to see data logs in Google Analytics. This may be because the GA dashboard doesn’t collect data from localhost. ## Checklist - [x] I have read https://github.com/jaegertracing/jaeger/blob/master/CONTRIBUTING_GUIDELINES.md - [x] I have signed all commits - [x] I have added unit tests for the new functionality - [x] I have run lint and test steps successfully - for `jaeger`: `make lint test` - for `jaeger-ui`: `npm test` --------- Signed-off-by: Muthukumar Signed-off-by: Yuri Shkuro Signed-off-by: Yuri Shkuro Signed-off-by: Avinash Signed-off-by: avinash Co-authored-by: Muthukumar Co-authored-by: Yuri Shkuro Co-authored-by: Yuri Shkuro --- package-lock.json | 86 +++++++++++++++++-- packages/jaeger-ui/package.json | 2 +- packages/jaeger-ui/src/types/tracking.tsx | 4 +- .../jaeger-ui/src/utils/tracking/README.md | 20 +++++ ...o-ga.test.js => conv-sentry-to-ga.test.js} | 12 +-- ...-raven-to-ga.tsx => conv-sentry-to-ga.tsx} | 69 +++++++-------- .../jaeger-ui/src/utils/tracking/fixtures.js | 4 +- .../jaeger-ui/src/utils/tracking/ga.test.js | 4 +- packages/jaeger-ui/src/utils/tracking/ga.tsx | 66 +++++++++----- 9 files changed, 183 insertions(+), 84 deletions(-) rename packages/jaeger-ui/src/utils/tracking/{conv-raven-to-ga.test.js => conv-sentry-to-ga.test.js} (65%) rename packages/jaeger-ui/src/utils/tracking/{conv-raven-to-ga.tsx => conv-sentry-to-ga.tsx} (87%) diff --git a/package-lock.json b/package-lock.json index 6b9e7ac048..bf17337df0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4028,6 +4028,81 @@ "dev": true, "license": "MIT" }, + "node_modules/@sentry-internal/browser-utils": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/browser-utils/-/browser-utils-8.44.0.tgz", + "integrity": "sha512-kmSRdS1r2G3i0wTJJv69uMZqf/UwP3pVqrCq/0hvNaF4L5v+vrEOKTDZghDvCqutEqOFXI0V/l9SuDpgjElcZQ==", + "license": "MIT", + "dependencies": { + "@sentry/core": "8.44.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/feedback": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/feedback/-/feedback-8.44.0.tgz", + "integrity": "sha512-x/7dilh9VRpsPRgx+1kT3Aulgj0X02GF+JfNeaFA2p786+2jBHTupGBu7AGiq1b1YRbDefkFXQxS1MaeqEEeOg==", + "license": "MIT", + "dependencies": { + "@sentry/core": "8.44.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/replay": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay/-/replay-8.44.0.tgz", + "integrity": "sha512-ZPX3Bg8ShuWZZzL5lw/fHjHdRhxxhhdzsVXq2jItg3CPvuO7oQofZsG4po6vgXTlj+fdtjUMQanj/6Ah4+jwsQ==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "8.44.0", + "@sentry/core": "8.44.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry-internal/replay-canvas": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@sentry-internal/replay-canvas/-/replay-canvas-8.44.0.tgz", + "integrity": "sha512-hFCUHDekuJknzVCu5JnDkgUuOTJbwu82RR+VfbT+2lfIpZoT+gH44LzSH5bQUPXgmznRae4OYHblWAPue9U1Bw==", + "license": "MIT", + "dependencies": { + "@sentry-internal/replay": "8.44.0", + "@sentry/core": "8.44.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/browser": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-8.44.0.tgz", + "integrity": "sha512-s12u8rz2aYjiWPzoE7StL7fiCS2Z5p5BYmk9bhGDqDWyAPVEVZFUB3u/hwcPUF4yDAroWCbsNzTiBwr813zihg==", + "license": "MIT", + "dependencies": { + "@sentry-internal/browser-utils": "8.44.0", + "@sentry-internal/feedback": "8.44.0", + "@sentry-internal/replay": "8.44.0", + "@sentry-internal/replay-canvas": "8.44.0", + "@sentry/core": "8.44.0" + }, + "engines": { + "node": ">=14.18" + } + }, + "node_modules/@sentry/core": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-8.44.0.tgz", + "integrity": "sha512-C43eW9Mr1WGpxCeI6pXUl7TeTwR2TwWhuU8wHx2s5eoATDQwbjz9l+JXXjVJf5YXXEwNOZL2WAx/f0diLA5rTQ==", + "license": "MIT", + "engines": { + "node": ">=14.18" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -15513,13 +15588,6 @@ "node": ">= 0.6" } }, - "node_modules/raven-js": { - "version": "3.27.2", - "resolved": "https://registry.npmjs.org/raven-js/-/raven-js-3.27.2.tgz", - "integrity": "sha512-mFWQcXnhRFEQe5HeFroPaEghlnqy7F5E2J3Fsab189ondqUzcjwSVi7el7F36cr6PvQYXoZ1P2F5CSF2/azeMQ==", - "deprecated": "Please upgrade to @sentry/browser. See the migration guide https://bit.ly/3ybOlo7", - "license": "BSD-2-Clause" - }, "node_modules/raw-body": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", @@ -20211,6 +20279,7 @@ "@ant-design/compatible": "^5.1.3", "@jaegertracing/plexus": "0.2.0", "@pyroscope/flamegraph": "0.21.4", + "@sentry/browser": "^8.18.0", "antd": "^5.21.3", "chance": "^1.0.10", "classnames": "^2.5.1", @@ -20230,7 +20299,6 @@ "object-hash": "^3.0.0", "prop-types": "^15.5.10", "query-string": "^9.0.0", - "raven-js": "^3.22.1", "react": "^18.3.1", "react-circular-progressbar": "^2.1.0", "react-dom": "^18.3.1", @@ -20328,7 +20396,7 @@ "rimraf": "6.0.1", "style-loader": "4.0.0", "url-loader": "4.1.1", - "webpack": "^5.97.1", + "webpack": "^5.92.0", "webpack-cli": "^5.1.4", "webpack-dev-server": "^5.0.4", "webpack-node-externals": "3.0.0", diff --git a/packages/jaeger-ui/package.json b/packages/jaeger-ui/package.json index f3b7905504..b4e0ffabd8 100644 --- a/packages/jaeger-ui/package.json +++ b/packages/jaeger-ui/package.json @@ -49,6 +49,7 @@ "@ant-design/compatible": "^5.1.3", "@jaegertracing/plexus": "0.2.0", "@pyroscope/flamegraph": "0.21.4", + "@sentry/browser": "^8.18.0", "antd": "^5.21.3", "chance": "^1.0.10", "classnames": "^2.5.1", @@ -68,7 +69,6 @@ "object-hash": "^3.0.0", "prop-types": "^15.5.10", "query-string": "^9.0.0", - "raven-js": "^3.22.1", "react": "^18.3.1", "react-circular-progressbar": "^2.1.0", "react-dom": "^18.3.1", diff --git a/packages/jaeger-ui/src/types/tracking.tsx b/packages/jaeger-ui/src/types/tracking.tsx index 9b0aea989a..1bf0b06f51 100644 --- a/packages/jaeger-ui/src/types/tracking.tsx +++ b/packages/jaeger-ui/src/types/tracking.tsx @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { RavenStatic } from 'raven-js'; +import { BrowserClient } from '@sentry/browser'; import { TNil } from '.'; import { Config } from './config'; @@ -22,7 +22,7 @@ export interface IWebAnalyticsFunc { export default interface IWebAnalytics { init: () => void; - context: boolean | RavenStatic | null; + context: boolean | typeof BrowserClient | null; isEnabled: () => boolean; trackPageView: (pathname: string, search: string | TNil) => void; trackError: (description: string) => void; diff --git a/packages/jaeger-ui/src/utils/tracking/README.md b/packages/jaeger-ui/src/utils/tracking/README.md index 59d6fc4fa9..44b7a63b5e 100644 --- a/packages/jaeger-ui/src/utils/tracking/README.md +++ b/packages/jaeger-ui/src/utils/tracking/README.md @@ -222,3 +222,23 @@ You get a lot for free when using Raven.js: - Some global handlers are added Implementing the above from scratch would require substantial effort. Meanwhile, Raven.js is well tested. + +### Steps to Verify the gaID Integration + +Setup: + +- Ensure you have the repository cloned and all dependencies installed. Run the following command in the terminal + - `npm install` +- Start the local development server + + - `npm start` This will open the application in your default browser + +Steps to put gaID: + +- Place the GA Measurement ID in `default-config.tsx`. + +- Verify navigation events using **TagHound**. Screenshots of the events being tracked are attached. + +- **Note**: Ensure GTM tracking is enabled in **TagHound**, otherwise the Google tags will not be tracked. + +- This test works in the local environment, but data logs may not appear in Google Analytics. This is likely because the GA dashboard doesn’t collect data from `localhost`. diff --git a/packages/jaeger-ui/src/utils/tracking/conv-raven-to-ga.test.js b/packages/jaeger-ui/src/utils/tracking/conv-sentry-to-ga.test.js similarity index 65% rename from packages/jaeger-ui/src/utils/tracking/conv-raven-to-ga.test.js rename to packages/jaeger-ui/src/utils/tracking/conv-sentry-to-ga.test.js index 58b6ef2661..e9a86df878 100644 --- a/packages/jaeger-ui/src/utils/tracking/conv-raven-to-ga.test.js +++ b/packages/jaeger-ui/src/utils/tracking/conv-sentry-to-ga.test.js @@ -12,12 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import convRavenToGa from './conv-raven-to-ga'; -import { RAVEN_PAYLOAD, RAVEN_TO_GA } from './fixtures'; +import convSentryToGa from './conv-sentry-to-ga'; +import { SENTRY_PAYLOAD, SENTRY_TO_GA } from './fixtures'; -describe('convRavenToGa()', () => { - it('converts the raven-js payload to { category, action, label, value }', () => { - const data = convRavenToGa(RAVEN_PAYLOAD); - expect(data).toEqual(RAVEN_TO_GA); +describe('convSentryToGa()', () => { + it('converts the sentry payload to { category, action, label, value }', () => { + const data = convSentryToGa(SENTRY_PAYLOAD); + expect(data).toEqual(SENTRY_TO_GA); }); }); diff --git a/packages/jaeger-ui/src/utils/tracking/conv-raven-to-ga.tsx b/packages/jaeger-ui/src/utils/tracking/conv-sentry-to-ga.tsx similarity index 87% rename from packages/jaeger-ui/src/utils/tracking/conv-raven-to-ga.tsx rename to packages/jaeger-ui/src/utils/tracking/conv-sentry-to-ga.tsx index 4b3a2bd0c6..62f1e1a510 100644 --- a/packages/jaeger-ui/src/utils/tracking/conv-raven-to-ga.tsx +++ b/packages/jaeger-ui/src/utils/tracking/conv-sentry-to-ga.tsx @@ -13,7 +13,7 @@ // limitations under the License. /* eslint-disable camelcase */ -import { RavenTransportOptions } from 'raven-js'; +import { Exception, Breadcrumb } from '@sentry/browser'; import prefixUrl from '../prefix-url'; @@ -116,20 +116,11 @@ function convErrorMessage(message: string, maxLen = 0) { // cFn // dFn // -interface IConvException { - type: string; - value: number; - stacktrace: { - frames: { - filename: string; - function: string; - }[]; - }; -} -function convException(errValue: IConvException) { + +function convException(errValue: Exception) { const message = convErrorMessage(`${errValue.type}: ${errValue.value}`, 149); - const frames = errValue.stacktrace.frames.map(fr => { - const filename = fr.filename.replace(origin, '').replace(/^\/static\/js\//i, ''); + const frames = (errValue.stacktrace?.frames ?? []).map(fr => { + const filename = (fr.filename ?? '').replace(origin, '').replace(/^\/static\/js\//i, ''); const fn = collapseWhitespace(fr.function || '??'); return { filename, fn }; }); @@ -169,9 +160,7 @@ function convNav(to: string) { // "dp" - fetch the dependency data // And, "NNN" is a non-200 status code. type TCrumbData = { - url: string; - status_code: number; - to: string; + [key: string]: any; }; function convFetch(data: TCrumbData) { const { url, status_code } = data; @@ -247,20 +236,14 @@ function compressCssSelector(selector: string) { // The chronological ordering of the breadcrumbs is older events precede newer // events. This ordering was kept because it's easier to see which page events // occurred on. -interface ICrumb { - category: string; - data: TCrumbData; - message: string; - selector: string; -} -function convBreadcrumbs(crumbs: ICrumb[]) { +function convBreadcrumbs(crumbs: Breadcrumb[]) { if (!Array.isArray(crumbs) || !crumbs.length) { return ''; } // the last UI breadcrumb has the CSS selector included let iLastUi = -1; for (let i = crumbs.length - 1; i >= 0; i--) { - if (crumbs[i].category.slice(0, 2) === 'ui') { + if (crumbs[i]?.category?.slice(0, 2) === 'ui') { iLastUi = i; break; } @@ -270,10 +253,10 @@ function convBreadcrumbs(crumbs: ICrumb[]) { let onNewLine = true; for (let i = 0; i < crumbs.length; i++) { const c = crumbs[i]; - const cStart = c.category.split('.')[0]; + const cStart = c.category?.split('.')[0]; switch (cStart) { case 'fetch': { - const fetched = convFetch(c.data); + const fetched = c.data ? convFetch(c.data) : null; if (fetched) { joiner.push(fetched); onNewLine = false; @@ -282,17 +265,17 @@ function convBreadcrumbs(crumbs: ICrumb[]) { } case 'navigation': { - const nav = `${onNewLine ? '' : '\n'}\n${convNav(c.data.to)}\n`; + const nav = `${onNewLine ? '' : '\n'}\n${convNav(c.data?.to)}\n`; joiner.push(nav); onNewLine = true; break; } case 'ui': { - if (i === iLastUi) { - const selector = compressCssSelector(c.message); + if (c.category && i === iLastUi) { + const selector = c.message ? compressCssSelector(c.message) : null; joiner.push(`${c.category[3]}{${selector}}`); - } else { + } else if (c.category) { joiner.push(c.category[3]); } onNewLine = false; @@ -300,7 +283,7 @@ function convBreadcrumbs(crumbs: ICrumb[]) { } case 'sentry': { - const msg = convErrorMessage(c.message, 58); + const msg = c.message ? convErrorMessage(c.message, 58) : null; joiner.push(`${onNewLine ? '' : '\n'}${msg}\n`); onNewLine = true; break; @@ -343,24 +326,32 @@ function convBreadcrumbs(crumbs: ICrumb[]) { // Create the GA label value from the message, page, duration, git info, and // breadcrumbs. See <./README.md> for details. -function getLabel(message: string, page: string, duration: number, git: string, breadcrumbs: ICrumb[]) { +function getLabel(message: string, page: string, duration: number, git: string, breadcrumbs: Breadcrumb[]) { const header = [message, page, duration, git, ''].filter(v => v != null).join('\n'); const crumbs = convBreadcrumbs(breadcrumbs); return `${header}\n${truncate(crumbs, 498 - header.length, true)}`; } -// Convert the Raven exception data to something that can be sent to Google +// Convert the exception data to something that can be sent to Google // Analytics. See <./README.md> for details. -export default function convRavenToGa({ data }: RavenTransportOptions) { +export default function convSentryToGa({ data }: { url: string; data: any }) { const { breadcrumbs, exception, extra, request, tags } = data; - const { message, stack } = convException(exception.values[0]); - const url = truncate(request.url.replace(origin, ''), 50); + const { message, stack } = convException(exception?.values?.[0] ?? {}); + + const url = truncate((request?.url ?? '').replace(origin, ''), 50); const { word: page } = getSym(NAV_SYMBOLS, url); - const value = Math.round(extra['session:duration'] / 1000); + const duration = extra?.['session:duration']; + const value = typeof duration === 'number' ? Math.round(duration / 1000) : 0; const category = `jaeger/${page}/error`; let action = [message, tags && tags.git, url, '', stack].filter(v => v != null).join('\n'); action = truncate(action, 499); - const label = getLabel(message, page, value, tags && tags.git, breadcrumbs && breadcrumbs.values); + const label = getLabel( + message, + page, + value, + (tags && tags.git) as string, + (breadcrumbs && Array.isArray(breadcrumbs.values) ? breadcrumbs.values : []) as Breadcrumb[] + ); return { message, category, diff --git a/packages/jaeger-ui/src/utils/tracking/fixtures.js b/packages/jaeger-ui/src/utils/tracking/fixtures.js index 818cde69d2..2ce3d82989 100644 --- a/packages/jaeger-ui/src/utils/tracking/fixtures.js +++ b/packages/jaeger-ui/src/utils/tracking/fixtures.js @@ -29,7 +29,7 @@ And the cloud that took the form Of a demon in my view—" 3/17/1829`; -export const RAVEN_PAYLOAD = deepFreeze({ +export const SENTRY_PAYLOAD = deepFreeze({ data: { request: { url: 'http://localhost/trace/565c1f00385ebd0b', @@ -212,7 +212,7 @@ tr ! Type! A very long message that will be truncated and re~ cic2i2i{.LabeledList.TracePageHeader--overviewItems}`; -export const RAVEN_TO_GA = deepFreeze({ +export const SENTRY_TO_GA = deepFreeze({ action, label, message: '! test-sentry', diff --git a/packages/jaeger-ui/src/utils/tracking/ga.test.js b/packages/jaeger-ui/src/utils/tracking/ga.test.js index dbe48d9bdd..9c85ed7680 100644 --- a/packages/jaeger-ui/src/utils/tracking/ga.test.js +++ b/packages/jaeger-ui/src/utils/tracking/ga.test.js @@ -15,7 +15,7 @@ import * as GA from './ga'; import { getAppEnvironment } from '../constants'; -jest.mock('./conv-raven-to-ga', () => () => ({ +jest.mock('./conv-sentry-to-ga', () => () => ({ category: 'jaeger/a', action: 'some-action', message: 'jaeger/a', @@ -166,7 +166,7 @@ describe('google analytics tracking', () => { }); }); - it('converting raven-js errors', () => { + it('converting sentry errors', () => { window.onunhandledrejection({ reason: new Error('abc'), }); diff --git a/packages/jaeger-ui/src/utils/tracking/ga.tsx b/packages/jaeger-ui/src/utils/tracking/ga.tsx index b7bdfa52ec..ac7891c32c 100644 --- a/packages/jaeger-ui/src/utils/tracking/ga.tsx +++ b/packages/jaeger-ui/src/utils/tracking/ga.tsx @@ -13,9 +13,15 @@ // limitations under the License. import _get from 'lodash/get'; -import Raven, { RavenOptions, RavenTransportOptions } from 'raven-js'; - -import convRavenToGa from './conv-raven-to-ga'; +import { + Event, + BrowserClient, + breadcrumbsIntegration, + captureException, + init as SentryInit, +} from '@sentry/browser'; + +import convSentryToGa from './conv-sentry-to-ga'; import { TNil } from '../../types'; import { Config } from '../../types/config'; import { IWebAnalyticsFunc } from '../../types/tracking'; @@ -30,6 +36,13 @@ interface WindowWithGATracking extends Window { dataLayer: (string | object)[][] | undefined; } +function convertEventToTransportOptions(event: Event): { url: string; data: any } { + return { + url: event.request?.url || '', + data: event, + }; +} + declare let window: WindowWithGATracking; const isTruish = (value?: string | string[]) => { @@ -47,7 +60,7 @@ const GA: IWebAnalyticsFunc = (config: Config, versionShort: string, versionLong const gaID = _get(config, 'tracking.gaID'); const isErrorsEnabled = isDebugMode || Boolean(_get(config, 'tracking.trackErrors')); const cookiesToDimensions = _get(config, 'tracking.cookiesToDimensions'); - const context = isErrorsEnabled ? Raven : null; + const context = isErrorsEnabled ? BrowserClient : null; const EVENT_LENGTHS = { action: 499, category: 149, @@ -119,8 +132,8 @@ const GA: IWebAnalyticsFunc = (config: Config, versionShort: string, versionLong }); }; - const trackRavenError = (ravenData: RavenTransportOptions) => { - const { message, category, action, label, value } = convRavenToGa(ravenData); + const trackSentryError = (sentryData: { url: string; data: any }) => { + const { message, category, action, label, value } = convSentryToGa(sentryData); trackError(message); trackEvent(category, action, label, value); }; @@ -164,24 +177,31 @@ const GA: IWebAnalyticsFunc = (config: Config, versionShort: string, versionLong ); } if (isErrorsEnabled) { - const ravenConfig: RavenOptions = { - autoBreadcrumbs: { - xhr: true, - console: false, - dom: true, - location: true, + SentryInit({ + dsn: 'https://fakedsn@omg.com/1', + environment: getAppEnvironment() || 'unknown', + integrations: [ + breadcrumbsIntegration({ + xhr: true, + console: false, + dom: true, + }), + ], + beforeSend(event) { + const transportOptions = convertEventToTransportOptions(event); + trackSentryError(transportOptions); + return event; }, - environment: getAppEnvironment() || 'unkonwn', - transport: trackRavenError, - }; - if (versionShort && versionShort !== 'unknown') { - ravenConfig.tags = { - git: versionShort, - }; - } - Raven.config('https://fakedsn@omg.com/1', ravenConfig).install(); - window.onunhandledrejection = function trackRejectedPromise(evt: PromiseRejectionEvent) { - Raven.captureException(evt.reason); + ...(versionShort && + versionShort !== 'unknown' && { + tags: { + git: versionShort, + }, + }), + }); + + window.onunhandledrejection = function trackRejectedPromise(evt) { + captureException(evt.reason); }; } };