From 784b485020b91aac4f2ac0160ae6df27d91304d9 Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Thu, 29 Feb 2024 14:08:02 +0100 Subject: [PATCH 01/17] feat(browser): Prevent initialization in browser extensions (#10844) Prevents initialization inside chrome.* and browser.* extension environments. Also refactored init() in browser because of eslint warning about too much complexity. Fixes https://github.com/getsentry/sentry-javascript/issues/10632 --- .../skip-init-browser-extension/init.js | 10 +++ .../skip-init-browser-extension/test.ts | 34 ++++++++++ .../skip-init-chrome-extension/init.js | 10 +++ .../skip-init-chrome-extension/test.ts | 31 +++++++++ packages/astro/test/client/sdk.test.ts | 4 +- packages/browser/src/sdk.ts | 64 +++++++++++++------ packages/browser/test/unit/sdk.test.ts | 58 +++++++++++++++++ 7 files changed, 190 insertions(+), 21 deletions(-) create mode 100644 dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/init.js create mode 100644 dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/test.ts create mode 100644 dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/init.js create mode 100644 dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/test.ts diff --git a/dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/init.js b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/init.js new file mode 100644 index 000000000000..7494359a0a8a --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +// We mock this here to simulate a Firefox/Safari browser extension +window.browser = { runtime: { id: 'mock-extension-id' } }; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', +}); diff --git a/dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/test.ts b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/test.ts new file mode 100644 index 000000000000..41d2da3e9e9d --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-browser-extension/test.ts @@ -0,0 +1,34 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../utils/fixtures'; + +sentryTest( + 'should not initialize when inside a Firefox/Safari browser extension', + async ({ getLocalTestUrl, page }) => { + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const errorLogs: string[] = []; + + page.on('console', message => { + if (message.type() === 'error') errorLogs.push(message.text()); + }); + + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + const isInitialized = await page.evaluate(() => { + return !!(window as any).Sentry.isInitialized(); + }); + + expect(isInitialized).toEqual(false); + expect(errorLogs.length).toEqual(1); + expect(errorLogs[0]).toEqual( + '[Sentry] You cannot run Sentry this way in a browser extension, check: https://docs.sentry.io/platforms/javascript/troubleshooting/#setting-up-sentry-in-shared-environments-eg-browser-extensions', + ); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/init.js b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/init.js new file mode 100644 index 000000000000..6cb3b49ceb53 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/init.js @@ -0,0 +1,10 @@ +import * as Sentry from '@sentry/browser'; + +window.Sentry = Sentry; + +// We mock this here to simulate a Chrome browser extension +window.chrome = { runtime: { id: 'mock-extension-id' } }; + +Sentry.init({ + dsn: 'https://public@dsn.ingest.sentry.io/1337', +}); diff --git a/dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/test.ts b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/test.ts new file mode 100644 index 000000000000..401788b588a9 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/manual-client/skip-init-chrome-extension/test.ts @@ -0,0 +1,31 @@ +import { expect } from '@playwright/test'; +import { sentryTest } from '../../../utils/fixtures'; + +sentryTest('should not initialize when inside a Chrome browser extension', async ({ getLocalTestUrl, page }) => { + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const errorLogs: string[] = []; + + page.on('console', message => { + if (message.type() === 'error') errorLogs.push(message.text()); + }); + + const url = await getLocalTestUrl({ testDir: __dirname }); + await page.goto(url); + + const isInitialized = await page.evaluate(() => { + return !!(window as any).Sentry.isInitialized(); + }); + + expect(isInitialized).toEqual(false); + expect(errorLogs.length).toEqual(1); + expect(errorLogs[0]).toEqual( + '[Sentry] You cannot run Sentry this way in a browser extension, check: https://docs.sentry.io/platforms/javascript/troubleshooting/#setting-up-sentry-in-shared-environments-eg-browser-extensions', + ); +}); diff --git a/packages/astro/test/client/sdk.test.ts b/packages/astro/test/client/sdk.test.ts index 50f4e3c9e354..a344cd326bb6 100644 --- a/packages/astro/test/client/sdk.test.ts +++ b/packages/astro/test/client/sdk.test.ts @@ -78,7 +78,7 @@ describe('Sentry client SDK', () => { ...tracingOptions, }); - const integrationsToInit = browserInit.mock.calls[0][0]?.defaultIntegrations; + const integrationsToInit = browserInit.mock.calls[0][0]?.defaultIntegrations || []; const browserTracing = getClient()?.getIntegrationByName('BrowserTracing'); expect(integrationsToInit).not.toContainEqual(expect.objectContaining({ name: 'BrowserTracing' })); @@ -93,7 +93,7 @@ describe('Sentry client SDK', () => { enableTracing: true, }); - const integrationsToInit = browserInit.mock.calls[0][0]?.defaultIntegrations; + const integrationsToInit = browserInit.mock.calls[0][0]?.defaultIntegrations || []; const browserTracing = getClient()?.getIntegrationByName('BrowserTracing'); expect(integrationsToInit).not.toContainEqual(expect.objectContaining({ name: 'BrowserTracing' })); diff --git a/packages/browser/src/sdk.ts b/packages/browser/src/sdk.ts index 254276af335c..e747bd533699 100644 --- a/packages/browser/src/sdk.ts +++ b/packages/browser/src/sdk.ts @@ -11,6 +11,7 @@ import { import type { DsnLike, Integration, Options, UserFeedback } from '@sentry/types'; import { addHistoryInstrumentationHandler, + consoleSandbox, logger, stackParserFromStackParserOptions, supportsFetch, @@ -43,6 +44,40 @@ export function getDefaultIntegrations(_options: Options): Integration[] { ]; } +function applyDefaultOptions(optionsArg: BrowserOptions = {}): BrowserOptions { + const defaultOptions: BrowserOptions = { + defaultIntegrations: getDefaultIntegrations(optionsArg), + release: + typeof __SENTRY_RELEASE__ === 'string' // This allows build tooling to find-and-replace __SENTRY_RELEASE__ to inject a release value + ? __SENTRY_RELEASE__ + : WINDOW.SENTRY_RELEASE && WINDOW.SENTRY_RELEASE.id // This supports the variable that sentry-webpack-plugin injects + ? WINDOW.SENTRY_RELEASE.id + : undefined, + autoSessionTracking: true, + sendClientReports: true, + }; + + return { ...defaultOptions, ...optionsArg }; +} + +function shouldShowBrowserExtensionError(): boolean { + const windowWithMaybeChrome = WINDOW as typeof WINDOW & { chrome?: { runtime?: { id?: string } } }; + const isInsideChromeExtension = + windowWithMaybeChrome && + windowWithMaybeChrome.chrome && + windowWithMaybeChrome.chrome.runtime && + windowWithMaybeChrome.chrome.runtime.id; + + const windowWithMaybeBrowser = WINDOW as typeof WINDOW & { browser?: { runtime?: { id?: string } } }; + const isInsideBrowserExtension = + windowWithMaybeBrowser && + windowWithMaybeBrowser.browser && + windowWithMaybeBrowser.browser.runtime && + windowWithMaybeBrowser.browser.runtime.id; + + return !!isInsideBrowserExtension || !!isInsideChromeExtension; +} + /** * A magic string that build tooling can leverage in order to inject a release value into the SDK. */ @@ -94,26 +129,17 @@ declare const __SENTRY_RELEASE__: string | undefined; * * @see {@link BrowserOptions} for documentation on configuration options. */ -export function init(options: BrowserOptions = {}): void { - if (options.defaultIntegrations === undefined) { - options.defaultIntegrations = getDefaultIntegrations(options); - } - if (options.release === undefined) { - // This allows build tooling to find-and-replace __SENTRY_RELEASE__ to inject a release value - if (typeof __SENTRY_RELEASE__ === 'string') { - options.release = __SENTRY_RELEASE__; - } +export function init(browserOptions: BrowserOptions = {}): void { + const options = applyDefaultOptions(browserOptions); - // This supports the variable that sentry-webpack-plugin injects - if (WINDOW.SENTRY_RELEASE && WINDOW.SENTRY_RELEASE.id) { - options.release = WINDOW.SENTRY_RELEASE.id; - } - } - if (options.autoSessionTracking === undefined) { - options.autoSessionTracking = true; - } - if (options.sendClientReports === undefined) { - options.sendClientReports = true; + if (shouldShowBrowserExtensionError()) { + consoleSandbox(() => { + // eslint-disable-next-line no-console + console.error( + '[Sentry] You cannot run Sentry this way in a browser extension, check: https://docs.sentry.io/platforms/javascript/troubleshooting/#setting-up-sentry-in-shared-environments-eg-browser-extensions', + ); + }); + return; } if (DEBUG_BUILD) { diff --git a/packages/browser/test/unit/sdk.test.ts b/packages/browser/test/unit/sdk.test.ts index 75cb238d35bb..f4a04d088135 100644 --- a/packages/browser/test/unit/sdk.test.ts +++ b/packages/browser/test/unit/sdk.test.ts @@ -4,6 +4,7 @@ import type { Client, Integration } from '@sentry/types'; import { resolvedSyncPromise } from '@sentry/utils'; import type { BrowserOptions } from '../../src'; +import { WINDOW } from '../../src'; import { init } from '../../src/sdk'; const PUBLIC_DSN = 'https://username@domain/123'; @@ -127,4 +128,61 @@ describe('init', () => { expect(newIntegration.setupOnce as jest.Mock).toHaveBeenCalledTimes(1); expect(DEFAULT_INTEGRATIONS[1].setupOnce as jest.Mock).toHaveBeenCalledTimes(0); }); + + describe('initialization error in browser extension', () => { + const DEFAULT_INTEGRATIONS: Integration[] = [ + new MockIntegration('MockIntegration 0.1'), + new MockIntegration('MockIntegration 0.2'), + ]; + + const options = getDefaultBrowserOptions({ dsn: PUBLIC_DSN, defaultIntegrations: DEFAULT_INTEGRATIONS }); + + afterEach(() => { + Object.defineProperty(WINDOW, 'chrome', { value: undefined, writable: true }); + Object.defineProperty(WINDOW, 'browser', { value: undefined, writable: true }); + }); + + it('should log a browser extension error if executed inside a Chrome extension', () => { + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + Object.defineProperty(WINDOW, 'chrome', { + value: { runtime: { id: 'mock-extension-id' } }, + writable: true, + }); + + init(options); + + expect(consoleErrorSpy).toBeCalledTimes(1); + expect(consoleErrorSpy).toHaveBeenCalledWith( + '[Sentry] You cannot run Sentry this way in a browser extension, check: https://docs.sentry.io/platforms/javascript/troubleshooting/#setting-up-sentry-in-shared-environments-eg-browser-extensions', + ); + + consoleErrorSpy.mockRestore(); + }); + + it('should log a browser extension error if executed inside a Firefox/Safari extension', () => { + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + Object.defineProperty(WINDOW, 'browser', { value: { runtime: { id: 'mock-extension-id' } }, writable: true }); + + init(options); + + expect(consoleErrorSpy).toBeCalledTimes(1); + expect(consoleErrorSpy).toHaveBeenCalledWith( + '[Sentry] You cannot run Sentry this way in a browser extension, check: https://docs.sentry.io/platforms/javascript/troubleshooting/#setting-up-sentry-in-shared-environments-eg-browser-extensions', + ); + + consoleErrorSpy.mockRestore(); + }); + + it('should not log a browser extension error if executed inside regular browser environment', () => { + const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + + init(options); + + expect(consoleErrorSpy).toBeCalledTimes(0); + + consoleErrorSpy.mockRestore(); + }); + }); }); From a838c17219f0dd27ad11419755e3de03fc745456 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 29 Feb 2024 13:08:30 +0000 Subject: [PATCH 02/17] ref(core): Allow `number` as span `traceFlag` (#10855) To align this with OTEL types. --- packages/types/src/span.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index dfc5ad5f9e6f..cf4f117637ed 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -58,8 +58,8 @@ export interface SpanJSON { } // These are aligned with OpenTelemetry trace flags -type TraceFlagNone = 0x0; -type TraceFlagSampled = 0x1; +type TraceFlagNone = 0; +type TraceFlagSampled = 1; export type TraceFlag = TraceFlagNone | TraceFlagSampled; export interface SpanContextData { @@ -90,8 +90,9 @@ export interface SpanContextData { * sampled or not. When set, the least significant bit documents that the * caller may have recorded trace data. A caller who does not record trace * data out-of-band leaves this flag unset. + * We allow number here because otel also does, so we can't be stricter than them. */ - traceFlags: TraceFlag; + traceFlags: TraceFlag | number; // Note: we do not have traceState here, but this is optional in OpenTelemetry anyhow } From 25a783b7cdfaa2702e36e1e4c347742a72c74af0 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 29 Feb 2024 14:21:46 +0000 Subject: [PATCH 03/17] ref(core): Remove `status` field from Span (#10856) This removes the public field to get/set `status` on a span/transaction. --- .../bun/test/integrations/bunserver.test.ts | 4 +- packages/core/src/tracing/sentrySpan.ts | 21 ----- packages/core/test/lib/tracing/errors.test.ts | 10 --- .../test/lib/tracing/idletransaction.test.ts | 14 ++-- packages/core/test/lib/tracing/trace.test.ts | 67 +++++++++------- .../core/test/lib/utils/spanUtils.test.ts | 2 +- packages/node/test/handlers.test.ts | 2 - .../node/test/integrations/undici.test.ts | 2 +- .../test/spanprocessor.test.ts | 10 --- packages/opentelemetry/src/spanExporter.ts | 5 +- packages/sveltekit/src/client/load.ts | 1 - packages/sveltekit/src/server/handle.ts | 1 - packages/sveltekit/src/server/load.ts | 2 - packages/sveltekit/test/client/load.test.ts | 2 - packages/sveltekit/test/server/handle.test.ts | 80 ++++++++++--------- packages/sveltekit/test/server/load.test.ts | 6 -- packages/types/src/span.ts | 15 ---- 17 files changed, 95 insertions(+), 149 deletions(-) diff --git a/packages/bun/test/integrations/bunserver.test.ts b/packages/bun/test/integrations/bunserver.test.ts index 35aac412717f..14080fa02315 100644 --- a/packages/bun/test/integrations/bunserver.test.ts +++ b/packages/bun/test/integrations/bunserver.test.ts @@ -24,7 +24,7 @@ describe('Bun Serve Integration', () => { test('generates a transaction around a request', async () => { client.on('finishTransaction', transaction => { - expect(transaction.status).toBe('ok'); + expect(spanToJSON(transaction).status).toBe('ok'); expect(spanToJSON(transaction).data?.['http.response.status_code']).toEqual(200); expect(spanToJSON(transaction).op).toEqual('http.server'); expect(spanToJSON(transaction).description).toEqual('GET /'); @@ -44,7 +44,7 @@ describe('Bun Serve Integration', () => { test('generates a post transaction', async () => { client.on('finishTransaction', transaction => { - expect(transaction.status).toBe('ok'); + expect(spanToJSON(transaction).status).toBe('ok'); expect(spanToJSON(transaction).data?.['http.response.status_code']).toEqual(200); expect(spanToJSON(transaction).op).toEqual('http.server'); expect(spanToJSON(transaction).description).toEqual('POST /'); diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index b484e9c964ca..a478574b1c4e 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -135,9 +135,6 @@ export class SentrySpan implements SpanInterface { if ('sampled' in spanContext) { this._sampled = spanContext.sampled; } - if (spanContext.status) { - this._status = spanContext.status; - } if (spanContext.endTimestamp) { this._endTime = spanContext.endTimestamp; } @@ -260,24 +257,6 @@ export class SentrySpan implements SpanInterface { this._endTime = endTime; } - /** - * The status of the span. - * - * @deprecated Use `spanToJSON().status` instead to get the status. - */ - public get status(): SpanStatusType | string | undefined { - return this._status; - } - - /** - * The status of the span. - * - * @deprecated Use `.setStatus()` instead to set or update the status. - */ - public set status(status: SpanStatusType | string | undefined) { - this._status = status; - } - /* eslint-enable @typescript-eslint/member-ordering */ /** @inheritdoc */ diff --git a/packages/core/test/lib/tracing/errors.test.ts b/packages/core/test/lib/tracing/errors.test.ts index f4ea6dd09846..29377d3fed09 100644 --- a/packages/core/test/lib/tracing/errors.test.ts +++ b/packages/core/test/lib/tracing/errors.test.ts @@ -50,18 +50,12 @@ describe('registerErrorHandlers()', () => { registerErrorInstrumentation(); const transaction = startInactiveSpan({ name: 'test' })!; - // eslint-disable-next-line deprecation/deprecation - expect(transaction.status).toBe(undefined); expect(spanToJSON(transaction).status).toBe(undefined); mockErrorCallback({} as HandlerDataError); - // eslint-disable-next-line deprecation/deprecation - expect(transaction.status).toBe(undefined); expect(spanToJSON(transaction).status).toBe(undefined); mockUnhandledRejectionCallback({}); - // eslint-disable-next-line deprecation/deprecation - expect(transaction.status).toBe(undefined); expect(spanToJSON(transaction).status).toBe(undefined); transaction.end(); @@ -72,8 +66,6 @@ describe('registerErrorHandlers()', () => { startSpan({ name: 'test' }, span => { mockErrorCallback({} as HandlerDataError); - // eslint-disable-next-line deprecation/deprecation - expect(span!.status).toBe('internal_error'); expect(spanToJSON(span!).status).toBe('internal_error'); }); }); @@ -83,8 +75,6 @@ describe('registerErrorHandlers()', () => { startSpan({ name: 'test' }, span => { mockUnhandledRejectionCallback({}); - // eslint-disable-next-line deprecation/deprecation - expect(span!.status).toBe('internal_error'); expect(spanToJSON(span!).status).toBe('internal_error'); }); }); diff --git a/packages/core/test/lib/tracing/idletransaction.test.ts b/packages/core/test/lib/tracing/idletransaction.test.ts index 8f61b43164c3..fae249227bb0 100644 --- a/packages/core/test/lib/tracing/idletransaction.test.ts +++ b/packages/core/test/lib/tracing/idletransaction.test.ts @@ -232,12 +232,12 @@ describe('IdleTransaction', () => { // Regular SentrySpan - should not modified expect(spans[1].spanContext().spanId).toBe(regularSpan.spanContext().spanId); - expect(spans[1]['_endTime']).not.toBe(spanToJSON(transaction).timestamp); + expect(spanToJSON(spans[1]).timestamp).not.toBe(spanToJSON(transaction).timestamp); // Cancelled SentrySpan - has endtimestamp of transaction expect(spans[2].spanContext().spanId).toBe(cancelledSpan.spanContext().spanId); - expect(spans[2].status).toBe('cancelled'); - expect(spans[2]['_endTime']).toBe(spanToJSON(transaction).timestamp); + expect(spanToJSON(spans[2]).status).toBe('cancelled'); + expect(spanToJSON(spans[2]).timestamp).toBe(spanToJSON(transaction).timestamp); } }); @@ -415,22 +415,22 @@ describe('IdleTransaction', () => { const transaction = new IdleTransaction({ name: 'foo' }, getCurrentHub(), 20000); const mockFinish = jest.spyOn(transaction, 'end'); - expect(transaction.status).not.toEqual('deadline_exceeded'); + expect(spanToJSON(transaction).status).not.toEqual('deadline_exceeded'); expect(mockFinish).toHaveBeenCalledTimes(0); // Beat 1 jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); - expect(transaction.status).not.toEqual('deadline_exceeded'); + expect(spanToJSON(transaction).status).not.toEqual('deadline_exceeded'); expect(mockFinish).toHaveBeenCalledTimes(0); // Beat 2 jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); - expect(transaction.status).not.toEqual('deadline_exceeded'); + expect(spanToJSON(transaction).status).not.toEqual('deadline_exceeded'); expect(mockFinish).toHaveBeenCalledTimes(0); // Beat 3 jest.advanceTimersByTime(TRACING_DEFAULTS.heartbeatInterval); - expect(transaction.status).not.toEqual('deadline_exceeded'); + expect(spanToJSON(transaction).status).not.toEqual('deadline_exceeded'); expect(mockFinish).toHaveBeenCalledTimes(0); }); diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index b02a4d2bb0fc..56f15b6dcce4 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -1,4 +1,4 @@ -import type { Event, Span as SpanType } from '@sentry/types'; +import type { Event, Span } from '@sentry/types'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, addTracingExtensions, @@ -7,6 +7,7 @@ import { getGlobalScope, getIsolationScope, setCurrentClient, + spanIsSampled, spanToJSON, withScope, } from '../../../src'; @@ -18,6 +19,7 @@ import { startSpan, startSpanManual, } from '../../../src/tracing'; +import { getSpanTree } from '../../../src/tracing/utils'; import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; beforeAll(() => { @@ -92,9 +94,9 @@ describe('startSpan', () => { }); it('creates a transaction', async () => { - let ref: any = undefined; + let _span: Span | undefined = undefined; client.on('finishTransaction', transaction => { - ref = transaction; + _span = transaction; }); try { await startSpan({ name: 'GET users/[id]' }, () => { @@ -103,16 +105,16 @@ describe('startSpan', () => { } catch (e) { // } - expect(ref).toBeDefined(); + expect(_span).toBeDefined(); - expect(spanToJSON(ref).description).toEqual('GET users/[id]'); - expect(ref.status).toEqual(isError ? 'internal_error' : undefined); + expect(spanToJSON(_span!).description).toEqual('GET users/[id]'); + expect(spanToJSON(_span!).status).toEqual(isError ? 'internal_error' : undefined); }); it('allows traceparent information to be overriden', async () => { - let ref: any = undefined; + let _span: Span | undefined = undefined; client.on('finishTransaction', transaction => { - ref = transaction; + _span = transaction; }); try { await startSpan( @@ -129,17 +131,17 @@ describe('startSpan', () => { } catch (e) { // } - expect(ref).toBeDefined(); + expect(_span).toBeDefined(); - expect(ref.sampled).toEqual(true); - expect(ref.traceId).toEqual('12345678901234567890123456789012'); - expect(ref.parentSpanId).toEqual('1234567890123456'); + expect(spanIsSampled(_span!)).toEqual(true); + expect(spanToJSON(_span!).trace_id).toEqual('12345678901234567890123456789012'); + expect(spanToJSON(_span!).parent_span_id).toEqual('1234567890123456'); }); it('allows for transaction to be mutated', async () => { - let ref: any = undefined; + let _span: Span | undefined = undefined; client.on('finishTransaction', transaction => { - ref = transaction; + _span = transaction; }); try { await startSpan({ name: 'GET users/[id]' }, span => { @@ -152,13 +154,13 @@ describe('startSpan', () => { // } - expect(spanToJSON(ref).op).toEqual('http.server'); + expect(spanToJSON(_span!).op).toEqual('http.server'); }); it('creates a span with correct description', async () => { - let ref: any = undefined; + let _span: Span | undefined = undefined; client.on('finishTransaction', transaction => { - ref = transaction; + _span = transaction; }); try { await startSpan({ name: 'GET users/[id]', parentSampled: true }, () => { @@ -170,16 +172,19 @@ describe('startSpan', () => { // } - expect(ref.spanRecorder.spans).toHaveLength(2); - expect(spanToJSON(ref.spanRecorder.spans[1]).description).toEqual('SELECT * from users'); - expect(ref.spanRecorder.spans[1].parentSpanId).toEqual(ref.spanId); - expect(ref.spanRecorder.spans[1].status).toEqual(isError ? 'internal_error' : undefined); + expect(_span).toBeDefined(); + const spans = getSpanTree(_span!); + + expect(spans).toHaveLength(2); + expect(spanToJSON(spans[1]).description).toEqual('SELECT * from users'); + expect(spanToJSON(spans[1]).parent_span_id).toEqual(_span!.spanContext().spanId); + expect(spanToJSON(spans[1]).status).toEqual(isError ? 'internal_error' : undefined); }); it('allows for span to be mutated', async () => { - let ref: any = undefined; + let _span: Span | undefined = undefined; client.on('finishTransaction', transaction => { - ref = transaction; + _span = transaction; }); try { await startSpan({ name: 'GET users/[id]', parentSampled: true }, () => { @@ -194,8 +199,11 @@ describe('startSpan', () => { // } - expect(ref.spanRecorder.spans).toHaveLength(2); - expect(spanToJSON(ref.spanRecorder.spans[1]).op).toEqual('db.query'); + expect(_span).toBeDefined(); + const spans = getSpanTree(_span!); + + expect(spans).toHaveLength(2); + expect(spanToJSON(spans[1]).op).toEqual('db.query'); }); it.each([ @@ -204,9 +212,9 @@ describe('startSpan', () => { // attribute should take precedence over top level origin { origin: 'manual', attributes: { 'sentry.origin': 'auto.http.browser' } }, ])('correctly sets the span origin', async () => { - let ref: any = undefined; + let _span: Span | undefined = undefined; client.on('finishTransaction', transaction => { - ref = transaction; + _span = transaction; }); try { await startSpan({ name: 'GET users/[id]', origin: 'auto.http.browser' }, () => { @@ -216,7 +224,8 @@ describe('startSpan', () => { // } - const jsonSpan = spanToJSON(ref); + expect(_span).toBeDefined(); + const jsonSpan = spanToJSON(_span!); expect(jsonSpan).toEqual({ data: { 'sentry.origin': 'auto.http.browser', @@ -944,7 +953,7 @@ describe('startInactiveSpan', () => { setCurrentClient(client); client.init(); - let span: SpanType | undefined; + let span: Span | undefined; withScope(scope => { scope.setTag('scope', 1); diff --git a/packages/core/test/lib/utils/spanUtils.test.ts b/packages/core/test/lib/utils/spanUtils.test.ts index bf89b99bc733..28a8e961c294 100644 --- a/packages/core/test/lib/utils/spanUtils.test.ts +++ b/packages/core/test/lib/utils/spanUtils.test.ts @@ -67,12 +67,12 @@ describe('spanToJSON', () => { op: 'test op', parentSpanId: '1234', spanId: '5678', - status: 'ok', traceId: 'abcd', origin: 'auto', startTimestamp: 123, endTimestamp: 456, }); + span.setStatus('ok'); expect(spanToJSON(span)).toEqual({ description: 'test name', diff --git a/packages/node/test/handlers.test.ts b/packages/node/test/handlers.test.ts index f813beea5389..3e65922faae1 100644 --- a/packages/node/test/handlers.test.ts +++ b/packages/node/test/handlers.test.ts @@ -407,8 +407,6 @@ describe('tracingHandler', () => { setImmediate(() => { expect(finishTransaction).toHaveBeenCalled(); - // eslint-disable-next-line deprecation/deprecation - expect(transaction.status).toBe('ok'); expect(spanToJSON(transaction).status).toBe('ok'); expect(spanToJSON(transaction).data).toEqual(expect.objectContaining({ 'http.response.status_code': 200 })); done(); diff --git a/packages/node/test/integrations/undici.test.ts b/packages/node/test/integrations/undici.test.ts index 2409515c08cb..381251d83edd 100644 --- a/packages/node/test/integrations/undici.test.ts +++ b/packages/node/test/integrations/undici.test.ts @@ -150,7 +150,7 @@ conditionalTest({ min: 16 })('Undici integration', () => { expect(spans.length).toBe(2); const span = spans[1]; - expect(span).toEqual(expect.objectContaining({ status: 'internal_error' })); + expect(spanToJSON(span).status).toEqual('internal_error'); }); }); diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts index 91da61c43118..d874a26fa1f0 100644 --- a/packages/opentelemetry-node/test/spanprocessor.test.ts +++ b/packages/opentelemetry-node/test/spanprocessor.test.ts @@ -361,14 +361,10 @@ describe('SentrySpanProcessor', () => { const transaction = getSpanForOtelSpan(otelSpan) as Transaction; // status is only set after end - // eslint-disable-next-line deprecation/deprecation - expect(transaction?.status).toBe(undefined); expect(spanToJSON(transaction!).status).toBe(undefined); otelSpan.end(); - // eslint-disable-next-line deprecation/deprecation - expect(transaction?.status).toBe('ok'); expect(spanToJSON(transaction!).status).toBe('ok'); }); @@ -379,14 +375,10 @@ describe('SentrySpanProcessor', () => { tracer.startActiveSpan('SELECT * FROM users;', child => { const sentrySpan = getSpanForOtelSpan(child); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.status).toBe(undefined); expect(spanToJSON(sentrySpan!).status).toBe(undefined); child.end(); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.status).toBe('ok'); expect(spanToJSON(sentrySpan!).status).toBe('ok'); parentOtelSpan.end(); @@ -469,8 +461,6 @@ describe('SentrySpanProcessor', () => { } otelSpan.end(); - // eslint-disable-next-line deprecation/deprecation - expect(transaction?.status).toBe(expected); expect(spanToJSON(transaction!).status).toBe(expected); }, ); diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index 2d58f9ffef2e..14f5fdc9fef8 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -176,7 +176,6 @@ function createTransactionForOtelSpan(span: ReadableSpan): Transaction { parentSampled, name: description, op, - status: mapStatus(span), startTimestamp: convertOtelTimeToSeconds(span.startTime), metadata: { ...dropUndefinedKeys({ @@ -190,6 +189,8 @@ function createTransactionForOtelSpan(span: ReadableSpan): Transaction { sampled: true, }); + transaction.setStatus(mapStatus(span)); + // We currently don't want to write this to the scope because it would mutate it. // In the future we will likely have some sort of transaction payload factory where we can pass this context in directly // eslint-disable-next-line deprecation/deprecation @@ -237,11 +238,11 @@ function createAndFinishSpanForOtelSpan(node: SpanNode, sentryParentSpan: Sentry name: description, op, data: allData, - status: mapStatus(span), startTimestamp: convertOtelTimeToSeconds(span.startTime), spanId, origin, }); + sentrySpan.setStatus(mapStatus(span)); node.children.forEach(child => { createAndFinishSpanForOtelSpan(child, sentrySpan, remaining); diff --git a/packages/sveltekit/src/client/load.ts b/packages/sveltekit/src/client/load.ts index 621015badbf1..3b3fba05fb07 100644 --- a/packages/sveltekit/src/client/load.ts +++ b/packages/sveltekit/src/client/load.ts @@ -94,7 +94,6 @@ export function wrapLoadWithSentry any>(origLoad: T) [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeId ? 'route' : 'url', }, name: routeId ? routeId : event.url.pathname, - status: 'ok', }, () => handleCallbackErrors(() => wrappingTarget.apply(thisArg, [patchedEvent]), sendErrorToSentry), ); diff --git a/packages/sveltekit/src/server/handle.ts b/packages/sveltekit/src/server/handle.ts index 988abf3604ec..f37742b2f09b 100644 --- a/packages/sveltekit/src/server/handle.ts +++ b/packages/sveltekit/src/server/handle.ts @@ -183,7 +183,6 @@ async function instrumentHandle( [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: event.route?.id ? 'route' : 'url', }, name: `${event.request.method} ${event.route?.id || event.url.pathname}`, - status: 'ok', ...traceparentData, metadata: { dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, diff --git a/packages/sveltekit/src/server/load.ts b/packages/sveltekit/src/server/load.ts index 6a8c62ccd7c5..6646ac5055bc 100644 --- a/packages/sveltekit/src/server/load.ts +++ b/packages/sveltekit/src/server/load.ts @@ -77,7 +77,6 @@ export function wrapLoadWithSentry any>(origLoad: T) [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeId ? 'route' : 'url', }, name: routeId ? routeId : event.url.pathname, - status: 'ok', }; try { @@ -144,7 +143,6 @@ export function wrapServerLoadWithSentry any>(origSe [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: routeId ? 'route' : 'url', }, name: routeId ? routeId : event.url.pathname, - status: 'ok', metadata: { dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, }, diff --git a/packages/sveltekit/test/client/load.test.ts b/packages/sveltekit/test/client/load.test.ts index 3c625865cf1c..34d1606174de 100644 --- a/packages/sveltekit/test/client/load.test.ts +++ b/packages/sveltekit/test/client/load.test.ts @@ -113,7 +113,6 @@ describe('wrapLoadWithSentry', () => { }, op: 'function.sveltekit.load', name: '/users/[id]', - status: 'ok', }, expect.any(Function), ); @@ -141,7 +140,6 @@ describe('wrapLoadWithSentry', () => { }, op: 'function.sveltekit.load', name: '/users/123', - status: 'ok', }, expect.any(Function), ); diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index 42e64affbcc1..fbaf0f1e0581 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -1,7 +1,8 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, addTracingExtensions, spanToJSON } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, addTracingExtensions, spanIsSampled, spanToJSON } from '@sentry/core'; +import type { Transaction as TransactionClass } from '@sentry/core'; import { NodeClient, setCurrentClient } from '@sentry/node-experimental'; import * as SentryNode from '@sentry/node-experimental'; -import type { Transaction } from '@sentry/types'; +import type { Span, Transaction } from '@sentry/types'; import type { Handle } from '@sveltejs/kit'; import { redirect } from '@sveltejs/kit'; import { vi } from 'vitest'; @@ -117,9 +118,9 @@ describe('handleSentry', () => { }); it("creates a transaction if there's no active span", async () => { - let ref: any = undefined; + let _span: Span | undefined = undefined; client.on('finishTransaction', (transaction: Transaction) => { - ref = transaction; + _span = transaction; }); try { @@ -128,22 +129,25 @@ describe('handleSentry', () => { // } - expect(ref).toBeDefined(); + expect(_span!).toBeDefined(); - expect(spanToJSON(ref).description).toEqual('GET /users/[id]'); - expect(spanToJSON(ref).op).toEqual('http.server'); - expect(ref.status).toEqual(isError ? 'internal_error' : 'ok'); - expect(ref.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); + expect(spanToJSON(_span!).description).toEqual('GET /users/[id]'); + expect(spanToJSON(_span!).op).toEqual('http.server'); + expect(spanToJSON(_span!).status).toEqual(isError ? 'internal_error' : 'ok'); + expect(spanToJSON(_span!).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); - expect(ref.endTimestamp).toBeDefined(); - expect(ref.spanRecorder.spans).toHaveLength(1); + expect(spanToJSON(_span!).timestamp).toBeDefined(); + + // eslint-disable-next-line deprecation/deprecation + const spans = (_span! as TransactionClass).spanRecorder?.spans; + expect(spans).toHaveLength(1); }); it('creates a child span for nested server calls (i.e. if there is an active span)', async () => { - let ref: any = undefined; + let _span: Span | undefined = undefined; let txnCount = 0; client.on('finishTransaction', (transaction: Transaction) => { - ref = transaction; + _span = transaction; ++txnCount; }); @@ -164,17 +168,19 @@ describe('handleSentry', () => { } expect(txnCount).toEqual(1); - expect(ref).toBeDefined(); + expect(_span!).toBeDefined(); + + expect(spanToJSON(_span!).description).toEqual('GET /users/[id]'); + expect(spanToJSON(_span!).op).toEqual('http.server'); + expect(spanToJSON(_span!).status).toEqual(isError ? 'internal_error' : 'ok'); + expect(spanToJSON(_span!).data?.[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); - expect(spanToJSON(ref).description).toEqual('GET /users/[id]'); - expect(spanToJSON(ref).op).toEqual('http.server'); - expect(ref.status).toEqual(isError ? 'internal_error' : 'ok'); - expect(ref.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toEqual('route'); + expect(spanToJSON(_span!).timestamp).toBeDefined(); - expect(ref.endTimestamp).toBeDefined(); + // eslint-disable-next-line deprecation/deprecation + const spans = (_span! as TransactionClass).spanRecorder?.spans?.map(spanToJSON); - expect(ref.spanRecorder.spans).toHaveLength(2); - const spans = ref.spanRecorder.spans.map(spanToJSON); + expect(spans).toHaveLength(2); expect(spans).toEqual( expect.arrayContaining([ expect.objectContaining({ op: 'http.server', description: 'GET /users/[id]' }), @@ -198,9 +204,9 @@ describe('handleSentry', () => { }, }); - let ref: any = undefined; + let _span: Span | undefined = undefined; client.on('finishTransaction', (transaction: Transaction) => { - ref = transaction; + _span = transaction; }); try { @@ -209,10 +215,10 @@ describe('handleSentry', () => { // } - expect(ref).toBeDefined(); - expect(ref.traceId).toEqual('1234567890abcdef1234567890abcdef'); - expect(ref.parentSpanId).toEqual('1234567890abcdef'); - expect(ref.sampled).toEqual(true); + expect(_span!).toBeDefined(); + expect(_span!.spanContext().traceId).toEqual('1234567890abcdef1234567890abcdef'); + expect(spanToJSON(_span!).parent_span_id).toEqual('1234567890abcdef'); + expect(spanIsSampled(_span!)).toEqual(true); }); it('creates a transaction with dynamic sampling context from baggage header', async () => { @@ -238,9 +244,9 @@ describe('handleSentry', () => { }, }); - let ref: any = undefined; + let _span: Span | undefined = undefined; client.on('finishTransaction', (transaction: Transaction) => { - ref = transaction; + _span = transaction; }); try { @@ -249,8 +255,8 @@ describe('handleSentry', () => { // } - expect(ref).toBeDefined(); - expect(ref.metadata.dynamicSamplingContext).toEqual({ + expect(_span!).toBeDefined(); + expect(_span.metadata.dynamicSamplingContext).toEqual({ environment: 'production', release: '1.0.0', public_key: 'dogsarebadatkeepingsecrets', @@ -302,9 +308,9 @@ describe('handleSentry', () => { }); it("doesn't create a transaction if there's no route", async () => { - let ref: any = undefined; + let _span: Span | undefined = undefined; client.on('finishTransaction', (transaction: Transaction) => { - ref = transaction; + _span = transaction; }); try { @@ -313,13 +319,13 @@ describe('handleSentry', () => { // } - expect(ref).toBeUndefined(); + expect(_span!).toBeUndefined(); }); it("Creates a transaction if there's no route but `handleUnknownRequests` is true", async () => { - let ref: any = undefined; + let _span: Span | undefined = undefined; client.on('finishTransaction', (transaction: Transaction) => { - ref = transaction; + _span = transaction; }); try { @@ -331,7 +337,7 @@ describe('handleSentry', () => { // } - expect(ref).toBeDefined(); + expect(_span!).toBeDefined(); }); }); }); diff --git a/packages/sveltekit/test/server/load.test.ts b/packages/sveltekit/test/server/load.test.ts index 906e12553500..8b8a5cbd80d4 100644 --- a/packages/sveltekit/test/server/load.test.ts +++ b/packages/sveltekit/test/server/load.test.ts @@ -203,7 +203,6 @@ describe('wrapLoadWithSentry calls trace', () => { }, op: 'function.sveltekit.load', name: '/users/[id]', - status: 'ok', }, expect.any(Function), ); @@ -222,7 +221,6 @@ describe('wrapLoadWithSentry calls trace', () => { }, op: 'function.sveltekit.load', name: '/users/123', - status: 'ok', }, expect.any(Function), ); @@ -258,7 +256,6 @@ describe('wrapServerLoadWithSentry calls trace', () => { name: '/users/[id]', parentSampled: true, parentSpanId: '1234567890abcdef', - status: 'ok', traceId: '1234567890abcdef1234567890abcdef', data: { 'http.method': 'GET', @@ -291,7 +288,6 @@ describe('wrapServerLoadWithSentry calls trace', () => { }, op: 'function.sveltekit.server.load', name: '/users/[id]', - status: 'ok', metadata: {}, data: { 'http.method': 'GET', @@ -316,7 +312,6 @@ describe('wrapServerLoadWithSentry calls trace', () => { name: '/users/[id]', parentSampled: true, parentSpanId: '1234567890abcdef', - status: 'ok', traceId: '1234567890abcdef1234567890abcdef', data: { 'http.method': 'GET', @@ -347,7 +342,6 @@ describe('wrapServerLoadWithSentry calls trace', () => { name: '/users/123', parentSampled: true, parentSpanId: '1234567890abcdef', - status: 'ok', traceId: '1234567890abcdef1234567890abcdef', data: { 'http.method': 'GET', diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index cf4f117637ed..1818b7e99995 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -109,12 +109,6 @@ export interface SpanContext { */ op?: string | undefined; - /** - * Completion status of the Span. - * See: {SpanStatusType} for possible values - */ - status?: string | undefined; - /** * Parent Span ID */ @@ -231,15 +225,6 @@ export interface Span extends Omit Date: Thu, 29 Feb 2024 14:52:10 +0000 Subject: [PATCH 04/17] feat(node): Remove unnecessary URL imports (#10860) Now we will no longer be supporting Node v8, these imports are no longer required --- packages/node-experimental/src/integrations/anr/index.ts | 1 - packages/node-experimental/src/integrations/spotlight.ts | 1 - .../node-experimental/src/integrations/tracing/hapi/types.ts | 3 +-- packages/node-experimental/src/proxy/helpers.ts | 2 -- packages/node-experimental/src/proxy/index.ts | 2 -- packages/node-experimental/src/transports/http-module.ts | 1 - packages/node-experimental/src/transports/http.ts | 1 - 7 files changed, 1 insertion(+), 10 deletions(-) diff --git a/packages/node-experimental/src/integrations/anr/index.ts b/packages/node-experimental/src/integrations/anr/index.ts index 2670f30db558..6e822e06b9a4 100644 --- a/packages/node-experimental/src/integrations/anr/index.ts +++ b/packages/node-experimental/src/integrations/anr/index.ts @@ -1,4 +1,3 @@ -import { URL } from 'url'; import { defineIntegration, getCurrentScope } from '@sentry/core'; import type { Contexts, Event, EventHint, IntegrationFn } from '@sentry/types'; import { logger } from '@sentry/utils'; diff --git a/packages/node-experimental/src/integrations/spotlight.ts b/packages/node-experimental/src/integrations/spotlight.ts index ebd8573c5072..21629ad340ac 100644 --- a/packages/node-experimental/src/integrations/spotlight.ts +++ b/packages/node-experimental/src/integrations/spotlight.ts @@ -1,5 +1,4 @@ import * as http from 'http'; -import { URL } from 'url'; import { defineIntegration } from '@sentry/core'; import type { Client, Envelope, IntegrationFn } from '@sentry/types'; import { logger, serializeEnvelope } from '@sentry/utils'; diff --git a/packages/node-experimental/src/integrations/tracing/hapi/types.ts b/packages/node-experimental/src/integrations/tracing/hapi/types.ts index a650667fe362..4da83f672076 100644 --- a/packages/node-experimental/src/integrations/tracing/hapi/types.ts +++ b/packages/node-experimental/src/integrations/tracing/hapi/types.ts @@ -19,7 +19,6 @@ // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/c73060bd14bb74a2f1906ccfc714d385863bc07d/types/boom/v4/index.d.ts import type * as stream from 'stream'; -import type * as url from 'url'; interface Podium { new (events?: Events[]): Podium; @@ -214,7 +213,7 @@ interface Request extends Podium { readonly path: string; response: ResponseObject | Boom | null; readonly route: RequestRoute; - readonly url: url.Url; + readonly url: URL; } interface ResponseObjectHeaderOptions { diff --git a/packages/node-experimental/src/proxy/helpers.ts b/packages/node-experimental/src/proxy/helpers.ts index 119ffd9317ce..2fa5b84f6ccb 100644 --- a/packages/node-experimental/src/proxy/helpers.ts +++ b/packages/node-experimental/src/proxy/helpers.ts @@ -30,8 +30,6 @@ import * as http from 'http'; import * as https from 'https'; import type { Readable } from 'stream'; -// TODO (v8): Remove this when Node < 12 is no longer supported -import type { URL } from 'url'; export type ThenableRequest = http.ClientRequest & { then: Promise['then']; diff --git a/packages/node-experimental/src/proxy/index.ts b/packages/node-experimental/src/proxy/index.ts index 4129a9f65cd7..89c4f4f5b4a2 100644 --- a/packages/node-experimental/src/proxy/index.ts +++ b/packages/node-experimental/src/proxy/index.ts @@ -33,8 +33,6 @@ import type * as http from 'http'; import type { OutgoingHttpHeaders } from 'http'; import * as net from 'net'; import * as tls from 'tls'; -// TODO (v8): Remove this when Node < 12 is no longer supported -import { URL } from 'url'; import { logger } from '@sentry/utils'; import { Agent } from './base'; import type { AgentConnectOpts } from './base'; diff --git a/packages/node-experimental/src/transports/http-module.ts b/packages/node-experimental/src/transports/http-module.ts index b4dd0492f4fd..26bb37b6f4b9 100644 --- a/packages/node-experimental/src/transports/http-module.ts +++ b/packages/node-experimental/src/transports/http-module.ts @@ -1,6 +1,5 @@ import type { ClientRequest, IncomingHttpHeaders, RequestOptions as HTTPRequestOptions } from 'http'; import type { RequestOptions as HTTPSRequestOptions } from 'https'; -import type { URL } from 'url'; export type HTTPModuleRequestOptions = HTTPRequestOptions | HTTPSRequestOptions | string | URL; diff --git a/packages/node-experimental/src/transports/http.ts b/packages/node-experimental/src/transports/http.ts index 83d8bab5141a..532367578fb5 100644 --- a/packages/node-experimental/src/transports/http.ts +++ b/packages/node-experimental/src/transports/http.ts @@ -1,7 +1,6 @@ import * as http from 'http'; import * as https from 'https'; import { Readable } from 'stream'; -import { URL } from 'url'; import { createGzip } from 'zlib'; import { createTransport } from '@sentry/core'; import type { From 95d2982f7adb7ed09c13e64928ef515b4b9432eb Mon Sep 17 00:00:00 2001 From: Abhijeet Prasad Date: Thu, 29 Feb 2024 10:29:47 -0500 Subject: [PATCH 05/17] docs(v8): Restructure migration docs (#10830) This is an attempt to restructure our migration docs in preparation for the first alpha. --- MIGRATION.md | 1770 ++++++++++++--------------------- docs/migration/v4-to-v5_v6.md | 404 ++++++++ docs/migration/v6-to-v7.md | 544 ++++++++++ 3 files changed, 1573 insertions(+), 1145 deletions(-) create mode 100644 docs/migration/v4-to-v5_v6.md create mode 100644 docs/migration/v6-to-v7.md diff --git a/MIGRATION.md b/MIGRATION.md index 0ab76f759ab7..30206739b42e 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,300 +1,724 @@ -# Upgrading from 7.x to 8.x +# Sentry JavaScript SDK Migration Docs -## Removal of `trackHeaders` option for Astro middleware +These docs walk through how to migrate our JavaScript SDKs through different major versions. -Instead of opting-in via the middleware config, you can configure if headers should be captured via -`requestDataIntegration` options, which defaults to `true` but can be disabled like this: +- Upgrading from [SDK 4.x to 5.x/6.x](./docs/migration/v4-to-v5_v6.md) +- Uprading from [SDK 6.x to 7.x](./docs/migration/v6-to-v7.md) +- Upgrading from [SDK 7.x to 8.x](./MIGRATION.md#upgrading-from-7x-to-8x) -``` -Sentry.init({ - integrations: [ - Sentry.requestDataIntegration({ - include: { - headers: false - }, - }), - ], -}); -``` +# Upgrading from 7.x to 8.x -## Removal of Client-Side health check transaction filters +The main goal of version 8 is to improve our performance monitoring APIs, integrations API, and ESM support. This +version is breaking because we removed deprecated APIs, restructured npm package contents, and introduced new +dependencies on OpenTelemetry. Below we will outline the steps you need to take to tackle to deprecated methods. -The SDK no longer filters out health check transactions by default. Instead, they are sent to Sentry but still dropped -by the Sentry backend by default. You can disable dropping them in your Sentry project settings. If you still want to -drop specific transactions within the SDK you can either use the `ignoreTransactions` SDK option. +Before updating to `8.x` of the SDK, we recommend upgrading to the latest version of `7.x`. You can then follow +[these steps](./MIGRATION.md#deprecations-in-7x) remove deprecated methods in `7.x` before upgrading to `8.x`. -## Removal of the `MetricsAggregator` integration class and `metricsAggregatorIntegration` +The v8 version of the JavaScript SDK requires a self-hosted version of Sentry TBD or higher (Will be chosen once first +stable release of `8.x` comes out). -The SDKs now support metrics features without any additional configuration. +## 1. Version Support changes: -## Updated behaviour of `tracePropagationTargets` in the browser (HTTP tracing headers & CORS) +**Node.js**: We now official support Node 14.8+ for our CJS package, and Node 18.8+ for our ESM package. This applies to +`@sentry/node` and all of our node-based server-side sdks (`@sentry/nextjs`, `@sentry/serverless`, etc.). We no longer +test against Node 8, 10, or 12 and cannot guarantee that the SDK will work as expected on these versions. -We updated the behaviour of the SDKs when no `tracePropagationTargets` option was defined. As a reminder, you can -provide a list of strings or RegExes that will be matched against URLs to tell the SDK, to which outgoing requests -tracing HTTP headers should be attached to. These tracing headers are used for distributed tracing. +**Browser**: Our browser SDKs (`@sentry/browser`, `@sentry/react`, `@sentry/vue`, etc.) now require ES6+ compatible +browsers. This means that we no longer support IE11 (end of an era). This also means that the Browser SDK requires the +fetch API to be available in the environment. -Previously, on the browser, when `tracePropagationTargets` were not defined, they defaulted to the following: -`['localhost', /^\/(?!\/)/]`. This meant that all request targets to that had "localhost" in the URL, or started with a -`/` were equipped with tracing headers. This default was chosen to prevent CORS errors in your browser applications. -However, this default had a few flaws. +New minimum supported browsers: -Going forward, when the `tracePropagationTargets` option is not set, tracing headers will be attached to all outgoing -requests on the same origin. For example, if you're on `https://example.com/` and you send a request to -`https://example.com/api`, the request will be traced (ie. will have trace headers attached). Requests to -`https://api.example.com/` will not, because it is on a different origin. The same goes for all applications running on -`localhost`. +- Chrome 51 +- Edge 15 +- Safari/iOS Safari 10 +- Firefox 54 +- Opera 38 +- Samnsung Internet 5 -When you provide a `tracePropagationTargets` option, all of the entries you defined will now be matched be matched -against the full URL of the outgoing request. Previously, it was only matched against what you called request APIs with. -For example, if you made a request like `fetch("/api/posts")`, the provided `tracePropagationTargets` were only compared -against `"/api/posts"`. Going forward they will be matched against the entire URL, for example, if you were on the page -`https://example.com/` and you made the same request, it would be matched against `"https://example.com/api/posts"`. +For IE11 support please transpile your code to ES5 using babel or similar and add required polyfills. -But that is not all. Because it would be annoying having to create matchers for the entire URL, if the request is a -same-origin request, we also match the `tracePropagationTargets` against the resolved `pathname` of the request. -Meaning, a matcher like `/^\/api/` would match a request call like `fetch('/api/posts')`, or -`fetch('https://same-origin.com/api/posts')` but not `fetch('https://different-origin.com/api/posts')`. +**React**: We no longer support React 15 in version 8 of the React SDK. -## Removal of the `tracingOrigins` option +## 2. Package removal -After its deprecation in v7 the `tracingOrigins` option is now removed in favor of the `tracePropagationTargets` option. -The `tracePropagationTargets` option should be set in the `Sentry.init()` options, or in your custom `Client`s option if -you create them. The `tracePropagationTargets` option can no longer be set in the `browserTracingIntegration()` options. +We've removed the following packages: -## Dropping Support for React 15 +- [@sentry/hub](./MIGRATION.md#sentryhub) +- [@sentry/tracing](./MIGRATION.md#sentrytracing) +- [@sentry/integrations](./MIGRATION.md#sentryintegrations) -Sentry will no longer officially support React 15 in version 8. This means that React 15.x will be removed -from`@sentry/react`'s peer dependencies. +#### @sentry/hub -## Removal of deprecated API in `@sentry/nextjs` +`@sentry/hub` has been removed and will no longer be published. All of the `@sentry/hub` exports have moved to +`@sentry/core`. -The following previously deprecated API has been removed from the `@sentry/nextjs` package: +#### @sentry/tracing -- `withSentryApi` (Replacement: `wrapApiHandlerWithSentry`) -- `withSentryAPI` (Replacement: `wrapApiHandlerWithSentry`) -- `withSentryGetServerSideProps` (Replacement: `wrapGetServerSidePropsWithSentry`) -- `withSentryGetStaticProps` (Replacement: `wrapGetStaticPropsWithSentry`) -- `withSentryServerSideGetInitialProps` (Replacement: `wrapGetInitialPropsWithSentry`) -- `withSentryServerSideAppGetInitialProps` (Replacement: `wrapAppGetInitialPropsWithSentry`) -- `withSentryServerSideDocumentGetInitialProps` (Replacement: `wrapDocumentGetInitialPropsWithSentry`) -- `withSentryServerSideErrorGetInitialProps` was renamed to `wrapErrorGetInitialPropsWithSentry` -- `nextRouterInstrumentation` (Replaced by using `browserTracingIntegration`) -- `IS_BUILD` -- `isBuild` +`@sentry/tracing` has been removed and will no longer be published. See +[below](./MIGRATION.md/#3-removal-of-deprecated-apis) for more details. -## Removal of `Span` class export from SDK packages +For Browser SDKs you can import `BrowserTracing` from the SDK directly: -In v8, we are no longer exporting the `Span` class from SDK packages (e.g. `@sentry/browser` or `@sentry/node`). -Internally, this class is now called `SentrySpan`, and it is no longer meant to be used by users directly. +```js +// Before (v7) +import * as Sentry from '@sentry/browser'; +import { BrowserTracing } from '@sentry/tracing'; -## Removal of Severity Enum +Sentry.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, + integrations: [new BrowserTracing()], +}); -In v7 we deprecated the `Severity` enum in favor of using the `SeverityLevel` type. In v8 we removed the `Severity` -enum. If you were using the `Severity` enum, you should replace it with the `SeverityLevel` type. See -[below](#severity-severitylevel-and-severitylevels) for code snippet examples +// After (v8) +import * as Sentry from '@sentry/browser'; -## Removal of the `Offline` integration +Sentry.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, + integrations: [new Sentry.BrowserTracing()], +}); +``` -The `Offline` integration has been removed in favor of the offline transport wrapper: -http://docs.sentry.io/platforms/javascript/configuration/transports/#offline-caching +If you were importing `@sentry/tracing` for the side effect, you can now use `Sentry.addTracingExtensions()` to add the +tracing extensions to the SDK. `addTracingExtensions` replaces the `addExtensionMethods` method from `@sentry/tracing`. -## Removal of `enableAnrDetection` and `Anr` class (##10562) +```js +// Before (v7) +import * as Sentry from '@sentry/browser'; +import '@sentry/tracing'; -The `enableAnrDetection` and `Anr` class have been removed. See the -[docs](https://docs.sentry.io/platforms/node/configuration/application-not-responding/) for more details how to migrate -to `anrIntegration`, the new integration for ANR detection. +Sentry.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, +}); -## Removal of `Sentry.configureScope` (#10565) +// After (v8) +import * as Sentry from '@sentry/browser'; -The top level `Sentry.configureScope` function has been removed. Instead, you should use the `Sentry.getCurrentScope()` -to access and mutate the current scope. +Sentry.addTracingExtensions(); -## Deletion of `@sentry/hub` package (#10530) +Sentry.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, +}); +``` -`@sentry/hub` has been removed. All exports from `@sentry/tracing` should be available in `@sentry/core` or in -`@sentry/browser` and `@sentry/node`. +For Node SDKs you no longer need the side effect import, you can remove all references to `@sentry/tracing`. -## Deletion of `@sentry/tracing` package +```js +// Before (v7) +const Sentry = require('@sentry/node'); +require('@sentry/tracing'); -`@sentry/tracing` has been removed. All exports from `@sentry/tracing` should be available in `@sentry/core` or in -`@sentry/browser` and `@sentry/node`. +Sentry.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, +}); -## Removal of `makeXHRTransport` transport (#10703) +// Before (v8) +const Sentry = require('@sentry/node'); -The `makeXHRTransport` transport has been removed. Only `makeFetchTransport` is available now. This means that the -Sentry SDK requires the fetch API to be available in the environment. +Sentry.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, +}); +``` -## General API Changes +#### @sentry/integrations -- The minumum supported Node version for all the SDK packages is Node 14 (#10527) -- Remove `spanStatusfromHttpCode` in favour of `getSpanStatusFromHttpCode` (#10361) -- Remove deprecated `deepReadDirSync` export from `@sentry/node` (#10564) -- Remove `_eventFromIncompleteOnError` usage (#10553) -- The `Transaction` integration in `@sentry/integrations` has been removed. There is no replacement API. (#10556) -- `extraErrorDataIntegration` now looks at - [`error.cause`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) by - default. +`@sentry/integrations` has been removed and will no longer be published. We moved pluggable integrations from their own +package (`@sentry/integrations`) to `@sentry/browser` and `@sentry/node`. in addition they are now functions instead of +classes. -## Integrations +```js +// Before (v7) +import { RewriteFrames } from '@sentry/integrations'; -We moved pluggable integrations from their own package (`@sentry/integrations`) to `@sentry/browser` and `@sentry/node`. +// After (v8) +import { rewriteFramesIntegration } from '@sentry/browser'; +``` Integrations that are now exported from `@sentry/browser` (or framework-specific packages like `@sentry/react`): -- httpClientIntegration -- contextLinesIntegration -- reportingObserverIntegration +- `httpClientIntegration` (`HTTPClient`) +- `contextLinesIntegration` (`ContextLines`) +- `reportingObserverIntegration` (`ReportingObserver`) Integrations that are now exported from `@sentry/node` and `@sentry/browser` (or framework-specific packages like `@sentry/react`): -- captureConsoleIntegration -- debugIntegration -- extraErrorDataIntegration -- rewriteFramesIntegration -- sessionTimingIntegration -- dedupeIntegration (enabled by default, not pluggable) +- `captureConsoleIntegration` (`CaptureConsole`) +- `debugIntegration` (`Debug`) +- `extraErrorDataIntegration` (`ExtraErrorData`) +- `rewriteFramesIntegration` (`RewriteFrames`) +- `sessionTimingIntegration` (`SessionTiming`) +- `dedupeIntegration` (`Dedupe`) - _Note: enabled by default, not pluggable_ -# Deprecations in 7.x +The `Transaction` integration has been removed from `@sentry/integrations`. There is no replacement API. -You can use the **Experimental** [@sentry/migr8](https://www.npmjs.com/package/@sentry/migr8) to automatically update -your SDK usage and fix most deprecations. This requires Node 18+. +## 3. Performance Monitoring Changes -```bash -npx @sentry/migr8@latest +- [Performance Monitoring API](./MIGRATION.md#performance-monitoring-api) +- [Performance Monitoring Integrations](./MIGRATION.md#performance-monitoring-integrations) + +### Performance Monitoring API + +The APIs for Performance Monitoring in the SDK have been revamped to align with OpenTelemetry, an open standard for +tracing and metrics. This allows us to provide a more consistent and powerful API for performance monitoring, and adds +support for a variety of new integrations out of the box for our Node SDK. + +Instead of using `startTransaction` and `span.startChild`, you rely on new helper methods to create spans top level +helpers to create spans. + +```js +// Measure how long a callback takes, `startSpan` returns the value of the callback. +// The span will become the "active span" for the duration of the callback. +const value = Sentry.startSpan({ name: 'mySpan' }, span => { + span.setAttribute('key', 'value'); + return expensiveFunction(); +}); + +// `startSpan` works with async callbacks as well - just make sure to return a promise! +const value = await Sentry.startSpan({ name: 'mySpan' }, async span => { + span.setAttribute('key', 'value'); + return await expensiveFunction(); +}); + +// You can nest spans via more `startSpan` calls. +const value = Sentry.startSpan({ name: 'mySpan' }, span => { + span.setAttribute('key1', 'value1'); + + // `nestedSpan` becomes the child of `mySpan`. + return Sentry.startSpan({ name: 'nestedSpan' }, nestedSpan => { + nestedSpan.setAttribute('key2', 'value2'); + return expensiveFunction(); + }); +}); + +// You can also create an inactive span that does not take a callback. +// Useful when you need to pass a span reference into another closure (like measuring duration between hooks). +const span = Sentry.startInactiveSpan({ name: 'mySpan' }); + +// Use `startSpanManual` if you want to manually control when to end the span +// Useful when you need to hook into event emitters or similar. +function middleware(res, req, next) { + return Sentry.startSpanManual({ name: 'mySpan' }, span => { + res.on('finish', () => { + span.end(); + }); + return next(); + }); +} ``` -This will let you select which updates to run, and automatically update your code. Make sure to still review all code -changes! +You can [read more about the new performance APIs here](./docs/v8-new-performance-apis.md). -## Depreacted `BrowserTracing` integration +To accomodate these changes, we're removed the following APIs: + +- [`startTransaction` and `span.startChild`](./MIGRATION.md#deprecate-starttransaction--spanstartchild) +- [Certain arguments in `startSpan` and `startTransaction`](./MIGRATION.md#deprecate-arguments-for-startspan-apis) +- [`scope.getSpan` and `scope.setSpan`](./MIGRATION.md#deprecate-scopegetspan-and-scopesetspan) +- [Variations of `continueTrace`](./MIGRATION.md#deprecate-variations-of-sentrycontinuetrace) + +We've also removed a variety of [top level fields](./MIGRATION.md#deprecated-fields-on-span-and-transaction) on the +`span` class. + +### Performance Monitoring Integrations + +As we added support for OpenTelemetry, we have expanded the automatic instrumentation for our Node.js SDK. We are adding +support for frameworks like Fastify, Nest.js, and Hapi, and expanding support for databases like Prisma and MongoDB via +Mongoose. + +We now support the following integrations out of the box: + +- `httpIntegration`: Automatically instruments Node `http` and `https` standard libraries +- `nativeNodeFetchIntegration`: Automatically instruments top level fetch and undici +- `expressIntegration`: Automatically instruments Express.js +- `fastifyIntegration`: Automatically instruments Fastify +- `hapiIntegration`: Automatically instruments Hapi +- `graphqlIntegration`: Automatically instruments GraphQL +- `mongoIntegration`: Automatically instruments MongoDB +- `mongooseIntegration`: Automatically instruments Mongoose +- `mysqlIntegration`: Automatically instruments MySQL +- `mysql2Integration`: Automatically instruments MySQL2 +- `nestIntegration`: Automatically instruments Nest.js +- `postgresIntegration`: Automatically instruments PostgreSQL +- `prismaIntegration`: Automatically instruments Prisma + +## 4. Removal of deprecated APIs + +- [General](./MIGRATION.md#general) +- [Browser SDK](./MIGRATION.md#browser-sdk-browser-react-vue-angular-ember-etc) +- [Server-side SDKs (Node, Deno, Bun)](./MIGRATION.md#server-side-sdks-node-deno-bun-etc) +- [Next.js SDK](./MIGRATION.md#nextjs-sdk) +- [Astro SDK](./MIGRATION.md#astro-sdk) + +### General + +Removed top-level exports: `tracingOrigins`, `MetricsAggregator`, `metricsAggregatorIntegration`, `Severity`, +`Sentry.configureScope`, `Span`, `spanStatusfromHttpCode`, `makeMain`, `lastEventId`, `pushScope`, `popScope`, +`addGlobalEventProcessor`, `timestampWithMs`, `addExtensionMethods` + +- [Deprecation of `Hub` and `getCurrentHub()`](./MIGRATION.md#deprecate-hub) +- [Removal of class-based integrations](./MIGRATION.md#removal-of-class-based-integrations) +- [`tracingOrigins` option replaced with `tracePropagationTargets`](./MIGRATION.md#tracingorigins-has-been-replaced-by-tracepropagationtargets) +- [Removal of `MetricsAggregator` and `metricsAggregatorIntegration`](./MIGRATION.md#removal-of-the-metricsaggregator-integration-class-and-metricsaggregatorintegration) +- [Removal of `Severity` Enum](./MIGRATION.md#removal-of-severity-enum) +- [Removal of `Sentry.configureScope` method](./MIGRATION.md#removal-of-sentryconfigurescope-method) +- [Removal of `Span` class export from SDK packages](./MIGRATION.md#removal-of-span-class-export-from-sdk-packages) +- [Removal of `spanStatusfromHttpCode` in favour of `getSpanStatusFromHttpCode`](./MIGRATION.md#removal-of-spanstatusfromhttpcode-in-favour-of-getspanstatusfromhttpcode) +- [Removal of `addGlobalEventProcessor` in favour of `addEventProcessor`](./MIGRATION.md#removal-of-addglobaleventprocessor-in-favour-of-addeventprocessor) +- [Removal of `lastEventId()` method](./MIGRATION.md#deprecate-lasteventid) + +#### Deprecation of `Hub` and `getCurrentHub()` -The `BrowserTracing` integration, together with the custom routing instrumentations passed to it, are deprecated in v8. -Instead, you should use `Sentry.browserTracingIntegration()`. +The `Hub` has been a very important part of the Sentry SDK API up until now. Hubs were the SDK's "unit of concurrency" +to keep track of data across threads and to scope data to certain parts of your code. Because it is overly complicated +and confusing to power users, it is going to be replaced by a set of new APIs: the "new Scope API". For now `Hub` and +`getCurrentHub` are still available, but it will be removed in the next major version. -Package-specific browser tracing integrations are available directly. In most cases, there is a single integration -provided for each package, which will make sure to set up performance tracing correctly for the given SDK. For react, we -provide multiple integrations to cover different router integrations: +See [Deprecate Hub](./MIGRATION.md#deprecate-hub) for details on how to replace existing usage of the Hub APIs. -### `@sentry/browser`, `@sentry/svelte`, `@sentry/gatsby` +The `hub.bindClient` and `makeMain` methods have been removed entirely, see +[initializing the SDK in v8](./docs/v8-initializing.md) for details how to work around this. + +#### Removal of class-based integrations + +In v7, integrations are classes and can be added as e.g. `integrations: [new Sentry.Replay()]`. In v8, integrations will +not be classes anymore, but instead functions. Both the use as a class, as well as accessing integrations from the +`Integrations.XXX` hash, is deprecated in favor of using the new functional integrations. For example, +`new Integrations.LinkedErrors()` becomes `linkedErrorsIntegration()`. + +For docs on the new integration interface, see [below](./MIGRATION.md#changed-integration-interface). + +For a list of integrations and their replacements, see +[below](./MIGRATION.md#list-of-integrations-and-their-replacements). + +The `getIntegration()` and `getIntegrationById()` have been removed entirely, see +[below](./MIGRATION.md#deprecate-getintegration-and-getintegrationbyid). ```js -import * as Sentry from '@sentry/browser'; +// Before (v7) +const replay = Sentry.getIntegration(Replay); + +// After (v8) +const replay = getClient().getIntegrationByName('Replay'); +``` + +#### `tracingOrigins` has been replaced by `tracePropagationTargets` + +`tracingOrigins` is now removed in favor of the `tracePropagationTargets` option. The `tracePropagationTargets` option +should be set in the `Sentry.init()` options, or in your custom `Client`s option if you create them. We've also updated +the behavior of the `tracePropagationTargets` option for Browser SDKs, see +[below](./MIGRATION.md/#updated-behaviour-of-tracepropagationtargets-in-the-browser-http-tracing-headers--cors) for more +details. + +For example for the Browser SDKs: +```ts +// Before (v7) +Sentry.init({ + dsn: '__DSN__', + integrations: [new Sentry.BrowserTracing({ tracingOrigins: ['localhost', 'example.com'] })], +}); + +// After (v8) Sentry.init({ + dsn: '__DSN__', integrations: [Sentry.browserTracingIntegration()], + tracePropagationTargets: ['localhost', 'example.com'], }); ``` -### `@sentry/react` +#### Removal of the `MetricsAggregator` integration class and `metricsAggregatorIntegration` -```js -import * as Sentry from '@sentry/react'; +The SDKs now support metrics features without any additional configuration. +```ts +// Before (v7) +// Server (Node/Deno/Bun) Sentry.init({ - integrations: [ - // No react router - Sentry.browserTracingIntegration(), - // OR, if you are using react router, instead use one of the following: - Sentry.reactRouterV6BrowserTracingIntegration({ - useEffect, - useLocation, - useNavigationType, - createRoutesFromChildren, - matchRoutes, - stripBasename, - }), - Sentry.reactRouterV5BrowserTracingIntegration({ - history, - }), - Sentry.reactRouterV4BrowserTracingIntegration({ - history, - }), - Sentry.reactRouterV3BrowserTracingIntegration({ - history, - routes, - match, - }), - ], + dsn: '__DSN__', + _experiments: { + metricsAggregator: true, + }, }); -``` - -### `@sentry/vue` -```js -import * as Sentry from '@sentry/vue'; +// Before (v7) +// Browser +Sentry.init({ + dsn: '__DSN__', + integrations: [Sentry.metricsAggregatorIntegration()], +}); +// After (v8) Sentry.init({ - integrations: [ - Sentry.browserTracingIntegration({ - // pass router in, if applicable - router, - }), - ], + dsn: '__DSN__', }); ``` -### `@sentry/angular` & `@sentry/angular-ivy` +#### Removal of Severity Enum + +In v7 we deprecated the `Severity` enum in favor of using the `SeverityLevel` type as this helps save bundle size, and +this has been removed in v8. You should now use the `SeverityLevel` type directly. ```js -import * as Sentry from '@sentry/angular'; +// Before (v7) +import { Severity, SeverityLevel } from '@sentry/types'; -Sentry.init({ - integrations: [Sentry.browserTracingIntegration()], +const levelA = Severity.error; + +const levelB: SeverityLevel = "error" + +// After (v8) +import { SeverityLevel } from '@sentry/types'; + +const levelA = "error" as SeverityLevel; + +const levelB: SeverityLevel = "error" +``` + +#### Removal of `Sentry.configureScope` method + +The top level `Sentry.configureScope` function has been removed. Instead, you should use the `Sentry.getCurrentScope()` +to access and mutate the current scope. + +```js +// Before (v7) +Sentry.configureScope(scope => { + scope.setTag('key', 'value'); }); -// You still need to add the Trace Service like before! +// After (v8) +Sentry.getCurrentScope().setTag('key', 'value'); ``` -### `@sentry/remix` +#### Removal of `Span` class export from SDK packages + +In v8, we are no longer exporting the `Span` class from SDK packages (e.g. `@sentry/browser` or `@sentry/node`). +Internally, this class is now called `SentrySpan`, and it is no longer meant to be used by users directly. + +#### Removal of `spanStatusfromHttpCode` in favour of `getSpanStatusFromHttpCode` + +In v8, we are removing the `spanStatusfromHttpCode` function in favor of `getSpanStatusFromHttpCode`. ```js -import * as Sentry from '@sentry/remix'; +// Before (v7) +const spanStatus = spanStatusfromHttpCode(200); -Sentry.init({ - integrations: [ - Sentry.browserTracingIntegration({ - useEffect, - useLocation, - useMatches, - }), - ], -}); +// After (v8) +const spanStatus = getSpanStatusFromHttpCode(200); ``` -### `@sentry/nextjs`, `@sentry/astro`, `@sentry/sveltekit` +#### Removal of `addGlobalEventProcessor` in favour of `addEventProcessor` -Browser tracing is automatically set up for you in these packages. If you need to customize the options, you can do it -like this: +In v8, we are removing the `addGlobalEventProcessor` function in favor of `addEventProcessor`. ```js -import * as Sentry from '@sentry/nextjs'; +// Before (v7) +addGlobalEventProcessor(event => { + delete event.extra; + return event; +}); -Sentry.init({ - integrations: [ - Sentry.browserTracingIntegration({ - // add custom options here - }), - ], +// After (v8) +addEventProcessor(event => { + delete event.extra; + return event; }); ``` -### `@sentry/ember` +#### Removal of `lastEventId()` method -Browser tracing is automatically set up for you. You can configure it as before through configuration. +The `lastEventId` function has been removed. See [below](./MIGRATION.md#deprecate-lasteventid) for more details. -## Deprecated `transactionContext` passed to `tracesSampler` +### Browser SDK (Browser, React, Vue, Angular, Ember, etc.) -Instead of an `transactionContext` being passed to the `tracesSampler` callback, the callback will directly receive -`name` and `attributes` going forward. You can use these to make your sampling decisions, while `transactionContext` -will be removed in v8. Note that the `attributes` are only the attributes at span creation time, and some attributes may -only be set later during the span lifecycle (and thus not be available during sampling). +Removed top-level exports: `Offline`, `makeXHRTransport`, `BrowserTracing` -## Deprecate using `getClient()` to check if the SDK was initialized +- [Removal of the `BrowserTracing` integration](./MIGRATION.md#removal-of-the-browsertracing-integration) +- [Removal of Offline integration](./MIGRATION.md#removal-of-the-offline-integration) +- [Removal of `makeXHRTransport` transport](./MIGRATION.md#removal-of-makexhrtransport-transport) -In v8, `getClient()` will stop returning `undefined` if `Sentry.init()` was not called. For cases where this may be used -to check if Sentry was actually initialized, using `getClient()` will thus not work anymore. Instead, you should use the -new `Sentry.isInitialized()` utility to check this. +#### Removal of the `BrowserTracing` integration + +The `BrowserTracing` integration, together with the custom routing instrumentations passed to it, are deprecated in v8. +Instead, you should use `Sentry.browserTracingIntegration()`. See examples +[below](./MIGRATION.md#deprecated-browsertracing-integration) + +#### Removal of the `Offline` integration + +The `Offline` integration has been removed in favor of the +[offline transport wrapper](http://docs.sentry.io/platforms/javascript/configuration/transports/#offline-caching). + +#### Removal of `makeXHRTransport` transport + +The `makeXHRTransport` transport has been removed. Only `makeFetchTransport` is available now. This means that the +Sentry SDK requires the fetch API to be available in the environment. + +### Server-side SDKs (Node, Deno, Bun, etc.) + +Removed top-level exports: `enableAnrDetection`, `Anr`, `deepReadDirSync` + +- [Removal of `enableAnrDetection` and `Anr` class](./MIGRATION.md#removal-of-enableanrdetection-and-anr-class) +- [Removal of `deepReadDirSync` method](./MIGRATION.md#removal-of-deepreaddirsync-method) + +#### Removal of `enableAnrDetection` and `Anr` class + +The `enableAnrDetection` and `Anr` class have been removed. See the +[docs](https://docs.sentry.io/platforms/node/configuration/application-not-responding/) for more details. PR: + +#### Removal of `deepReadDirSync` method + +The `deepReadDirSync` method has been removed. There is no replacement API. + +### Next.js SDK + +Removed top-level exports: `withSentryApi`, `withSentryAPI`, `withSentryGetServerSideProps`, `withSentryGetStaticProps`, +`withSentryServerSideGetInitialProps`, `withSentryServerSideAppGetInitialProps`, +`withSentryServerSideDocumentGetInitialProps`, `withSentryServerSideErrorGetInitialProps`, `nextRouterInstrumentation`, +`IS_BUILD`, `isBuild` + +- [Removal of deprecated API in `@sentry/nextjs`](./MIGRATION.md#removal-of-deprecated-api-in-sentrynextjs) + +#### Removal of deprecated API in `@sentry/nextjs` + +The following previously deprecated API has been removed from the `@sentry/nextjs` package: + +- `withSentryApi` (Replacement: `wrapApiHandlerWithSentry`) +- `withSentryAPI` (Replacement: `wrapApiHandlerWithSentry`) +- `withSentryGetServerSideProps` (Replacement: `wrapGetServerSidePropsWithSentry`) +- `withSentryGetStaticProps` (Replacement: `wrapGetStaticPropsWithSentry`) +- `withSentryServerSideGetInitialProps` (Replacement: `wrapGetInitialPropsWithSentry`) +- `withSentryServerSideAppGetInitialProps` (Replacement: `wrapAppGetInitialPropsWithSentry`) +- `withSentryServerSideDocumentGetInitialProps` (Replacement: `wrapDocumentGetInitialPropsWithSentry`) +- `withSentryServerSideErrorGetInitialProps` was renamed to `wrapErrorGetInitialPropsWithSentry` +- `nextRouterInstrumentation` (Replaced by using `browserTracingIntegration`) +- `IS_BUILD` +- `isBuild` + +### Astro SDK + +#### Removal of `trackHeaders` option for Astro middleware + +Instead of opting-in via the middleware config, you can configure if headers should be captured via +`requestDataIntegration` options, which defaults to `true` but can be disabled like this: + +```js +Sentry.init({ + integrations: [ + Sentry.requestDataIntegration({ + include: { + headers: false, + }, + }), + ], +}); +``` + +## 5. Behaviour Changes + +- [Updated behaviour of `tracePropagationTargets` in the browser](./MIGRATION.md#updated-behaviour-of-tracepropagationtargets-in-the-browser-http-tracing-headers--cors) +- [Updated behaviour of `extraErrorDataIntegration`](./MIGRATION.md#extraerrordataintegration-changes) +- [Updated behaviour of `transactionContext` passed to `tracesSampler`](./MIGRATION.md#transactioncontext-no-longer-passed-to-tracessampler) +- [Updated behaviour of `getClient()`](./MIGRATION.md#getclient-always-returns-a-client) +- [Removal of Client-Side health check transaction filters](./MIGRATION.md#removal-of-client-side-health-check-transaction-filters) + +#### Updated behaviour of `tracePropagationTargets` in the browser (HTTP tracing headers & CORS) + +We updated the behaviour of the SDKs when no `tracePropagationTargets` option was defined. As a reminder, you can +provide a list of strings or RegExes that will be matched against URLs to tell the SDK, to which outgoing requests +tracing HTTP headers should be attached to. These tracing headers are used for distributed tracing. + +Previously, on the browser, when `tracePropagationTargets` were not defined, they defaulted to the following: +`['localhost', /^\/(?!\/)/]`. This meant that all request targets to that had "localhost" in the URL, or started with a +`/` were equipped with tracing headers. This default was chosen to prevent CORS errors in your browser applications. +However, this default had a few flaws. + +Going forward, when the `tracePropagationTargets` option is not set, tracing headers will be attached to all outgoing +requests on the same origin. For example, if you're on `https://example.com/` and you send a request to +`https://example.com/api`, the request will be traced (ie. will have trace headers attached). Requests to +`https://api.example.com/` will not, because it is on a different origin. The same goes for all applications running on +`localhost`. + +When you provide a `tracePropagationTargets` option, all of the entries you defined will now be matched be matched +against the full URL of the outgoing request. Previously, it was only matched against what you called request APIs with. +For example, if you made a request like `fetch("/api/posts")`, the provided `tracePropagationTargets` were only compared +against `"/api/posts"`. Going forward they will be matched against the entire URL, for example, if you were on the page +`https://example.com/` and you made the same request, it would be matched against `"https://example.com/api/posts"`. + +But that is not all. Because it would be annoying having to create matchers for the entire URL, if the request is a +same-origin request, we also match the `tracePropagationTargets` against the resolved `pathname` of the request. +Meaning, a matcher like `/^\/api/` would match a request call like `fetch('/api/posts')`, or +`fetch('https://same-origin.com/api/posts')` but not `fetch('https://different-origin.com/api/posts')`. + +#### `extraErrorDataIntegration` changes + +The `extraErrorDataIntegration` integration now looks at +[`error.cause`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) by +default. + +#### `transactionContext` no longer passed to `tracesSampler` + +Instead of an `transactionContext` being passed to the `tracesSampler` callback, the callback will directly receive +`name` and `attributes` going forward. Note that the `attributes` are only the attributes at span creation time, and +some attributes may only be set later during the span lifecycle (and thus not be available during sampling). + +#### `getClient()` always returns a client + +`getClient()` now always returns a client if `Sentry.init()` was called. For cases where this may be used to check if +Sentry was actually initialized, using `getClient()` will thus not work anymore. Instead, you should use the new +`Sentry.isInitialized()` utility to check this. + +#### Removal of Client-Side health check transaction filters + +The SDK no longer filters out health check transactions by default. Instead, they are sent to Sentry but still dropped +by the Sentry backend by default. You can disable dropping them in your Sentry project settings. If you still want to +drop specific transactions within the SDK you can either use the `ignoreTransactions` SDK option. + +# Deprecations in 7.x + +You can use the **Experimental** [@sentry/migr8](https://www.npmjs.com/package/@sentry/migr8) to automatically update +your SDK usage and fix most deprecations. This requires Node 18+. + +```bash +npx @sentry/migr8@latest +``` + +This will let you select which updates to run, and automatically update your code. Make sure to still review all code +changes! + +## Deprecated `BrowserTracing` integration + +The `BrowserTracing` integration, together with the custom routing instrumentations passed to it, are deprecated in v8. +Instead, you should use `Sentry.browserTracingIntegration()`. + +Package-specific browser tracing integrations are available directly. In most cases, there is a single integration +provided for each package, which will make sure to set up performance tracing correctly for the given SDK. For react, we +provide multiple integrations to cover different router integrations: + +### `@sentry/browser`, `@sentry/svelte`, `@sentry/gatsby` + +```js +import * as Sentry from '@sentry/browser'; + +Sentry.init({ + integrations: [Sentry.browserTracingIntegration()], +}); +``` + +### `@sentry/react` + +```js +import * as Sentry from '@sentry/react'; + +Sentry.init({ + integrations: [ + // No react router + Sentry.browserTracingIntegration(), + // OR, if you are using react router, instead use one of the following: + Sentry.reactRouterV6BrowserTracingIntegration({ + useEffect, + useLocation, + useNavigationType, + createRoutesFromChildren, + matchRoutes, + stripBasename, + }), + Sentry.reactRouterV5BrowserTracingIntegration({ + history, + }), + Sentry.reactRouterV4BrowserTracingIntegration({ + history, + }), + Sentry.reactRouterV3BrowserTracingIntegration({ + history, + routes, + match, + }), + ], +}); +``` + +### `@sentry/vue` + +```js +import * as Sentry from '@sentry/vue'; + +Sentry.init({ + integrations: [ + Sentry.browserTracingIntegration({ + // pass router in, if applicable + router, + }), + ], +}); +``` + +### `@sentry/angular` & `@sentry/angular-ivy` + +```js +import * as Sentry from '@sentry/angular'; + +Sentry.init({ + integrations: [Sentry.browserTracingIntegration()], +}); + +// You still need to add the TraceService like before! +``` + +### `@sentry/remix` + +```js +import * as Sentry from '@sentry/remix'; + +Sentry.init({ + integrations: [ + Sentry.browserTracingIntegration({ + useEffect, + useLocation, + useMatches, + }), + ], +}); +``` + +### `@sentry/nextjs`, `@sentry/astro`, `@sentry/sveltekit` + +Browser tracing is automatically set up for you in these packages. If you need to customize the options, you can do it +like this: + +```js +import * as Sentry from '@sentry/nextjs'; + +Sentry.init({ + integrations: [ + Sentry.browserTracingIntegration({ + // add custom options here + }), + ], +}); +``` + +### `@sentry/ember` + +Browser tracing is automatically set up for you. You can configure it as before through configuration. + +## Deprecated `transactionContext` passed to `tracesSampler` + +Instead of an `transactionContext` being passed to the `tracesSampler` callback, the callback will directly receive +`name` and `attributes` going forward. You can use these to make your sampling decisions, while `transactionContext` +will be removed in v8. Note that the `attributes` are only the attributes at span creation time, and some attributes may +only be set later during the span lifecycle (and thus not be available during sampling). + +## Deprecate using `getClient()` to check if the SDK was initialized + +In v8, `getClient()` will stop returning `undefined` if `Sentry.init()` was not called. For cases where this may be used +to check if Sentry was actually initialized, using `getClient()` will thus not work anymore. Instead, you should use the +new `Sentry.isInitialized()` utility to check this. ## Deprecate `getCurrentHub()` @@ -314,6 +738,8 @@ integrations from the `Integrations.XXX` hash, is deprecated in favor of using t The following list shows how integrations should be migrated: +### List of integrations and their replacements + | Old | New | Packages | | ---------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------- | | `new BrowserTracing()` | `browserTracingIntegration()` | `@sentry/browser` | @@ -543,7 +969,6 @@ callback. ```js // Before - Sentry.init({ beforeSend(event, hint) { const lastCapturedEventId = Sentry.lastEventId(); @@ -783,948 +1208,3 @@ This release deprecates `@sentry/hub` and all of it's exports. All of the `@sent For details on upgrading Replay in its beta phase, please view the [dedicated Replay MIGRATION docs](./packages/replay/MIGRATION.md). - -# Upgrading from 6.x to 7.x - -The main goal of version 7 is to reduce bundle size. This version is breaking because we removed deprecated APIs, -upgraded our build tooling, and restructured npm package contents. Below we will outline all the breaking changes you -should consider when upgrading. - -**TL;DR** If you only use basic features of Sentry, or you simply copy & pasted the setup examples from our docs, here's -what changed for you: - -- If you installed additional Sentry packages, such as`@sentry/tracing` alongside your Sentry SDK (e.g. `@sentry/react` - or `@sentry/node`), make sure to upgrade all of them to version 7. -- Our CDN bundles are now ES6 - you will need to [reconfigure your script tags](#renaming-of-cdn-bundles) if you want to - keep supporting ES5 and IE11 on the new SDK version. -- Distributed CommonJS files will be ES6. Use a transpiler if you need to support old node versions. -- We bumped the TypeScript version we generate our types with to 3.8.3. Please check if your TypeScript projects using - TypeScript version 3.7 or lower still compile. Otherwise, upgrade your TypeScript version. -- `whitelistUrls` and `blacklistUrls` have been renamed to `allowUrls` and `denyUrls` in the `Sentry.init()` options. -- The `UserAgent` integration is now called `HttpContext`. -- If you are using Performance Monitoring and with tracing enabled, you might have to - [make adjustments to your server's CORS settings](#propagation-of-baggage-header) - -## Dropping Support for Node.js v6 - -Node.js version 6 has reached end of life in April 2019. For Sentry JavaScript SDK version 7, we will no longer be -supporting version 6 of Node.js. - -As far as SDK development goes, dropping support means no longer running integration tests for Node.js version 6, and -also no longer handling edge cases specific to version 6. Running the new SDK version on Node.js v6 is therefore highly -discouraged. - -## Removal of `@sentry/minimal` - -The `@sentry/minimal` package was deleted and it's functionality was moved to `@sentry/hub`. All exports from -`@sentry/minimal` should be available in `@sentry/hub` other than `_callOnClient` function which was removed. - -```ts -// New in v7: -import { addBreadcrumb, captureException, configureScope, setTag } from '@sentry/hub'; - -// Before: -import { addBreadcrumb, captureException, configureScope, setTag } from '@sentry/minimal'; -``` - -## Explicit Client Options - -In v7, we've updated the `Client` to have options separate from the options passed into `Sentry.init`. This means that -constructing a client now requires 3 options: `integrations`, `transport` and `stackParser`. These can be customized as -you see fit. - -```ts -import { BrowserClient, defaultStackParser, defaultIntegrations, makeFetchTransport } from '@sentry/browser'; - -// New in v7: -const client = new BrowserClient({ - transport: makeFetchTransport, - stackParser: defaultStackParser, - integrations: defaultIntegrations, -}); - -// Before: -const client = new BrowserClient(); -``` - -Since you now explicitly pass in the dependencies of the client, you can also tree-shake out dependencies that you do -not use this way. For example, you can tree-shake out the SDK's default integrations and only use the ones that you want -like so: - -```ts -import { - BrowserClient, - Breadcrumbs, - Dedupe, - defaultStackParser, - GlobalHandlers, - Integrations, - makeFetchTransport, - LinkedErrors, -} from '@sentry/browser'; - -// New in v7: -const client = new BrowserClient({ - transport: makeFetchTransport, - stackParser: defaultStackParser, - integrations: [new Breadcrumbs(), new GlobalHandlers(), new LinkedErrors(), new Dedupe()], -}); -``` - -## Removal Of Old Platform Integrations From `@sentry/integrations` Package - -The following classes will be removed from the `@sentry/integrations` package and can no longer be used: - -- `Angular` -- `Ember` -- `Vue` - -These classes have been superseded and were moved into their own packages, `@sentry/angular`, `@sentry/ember`, and -`@sentry/vue` in a previous version. Refer to those packages if you want to integrate Sentry into your Angular, Ember, -or Vue application. - -## Moving To ES6 For CommonJS Files - -From version 7 onwards, the CommonJS files in Sentry JavaScript SDK packages will use ES6. - -If you need to support Internet Explorer 11 or old Node.js versions, we recommend using a preprocessing tool like -[Babel](https://babeljs.io/) to convert Sentry packages to ES5. - -## Renaming Of CDN Bundles - -CDN bundles will be ES6 by default. Files that followed the naming scheme `bundle.es6.min.js` were renamed to -`bundle.min.js` and any bundles using ES5 (files without `.es6`) turned into `bundle.es5.min.js`. - -See our [docs on CDN bundles](https://docs.sentry.io/platforms/javascript/install/cdn/) for more information. - -## Restructuring Of Package Content - -Up until v6.x, we have published our packages on npm with the following structure: - -- `build` folder contained CDN bundles -- `dist` folder contained CommonJS files and TypeScript declarations -- `esm` folder contained ESM files and TypeScript declarations - -Moving forward the JavaScript SDK packages will generally have the following structure: - -- `cjs` folder contains CommonJS files -- `esm` folder contains ESM files -- `types` folder contains TypeScript declarations - -**CDN bundles of version 7 or higher will no longer be distributed through our npm package.** This means that most -third-party CDNs like [unpkg](https://unpkg.com/) or [jsDelivr](https://www.jsdelivr.com/) will also not provide them. - -If you depend on any specific files in a Sentry JavaScript npm package, you will most likely need to update their -references. For example, imports on `@sentry/browser/dist/client` will become `@sentry/browser/cjs/client`. However, -directly importing from specific files is discouraged. - -## Removing the `API` class from `@sentry/core` - -The internal `API` class was removed in favor of using client options explicitly. - -```js -// New in v7: -import { - initAPIDetails, - getEnvelopeEndpointWithUrlEncodedAuth, - getStoreEndpointWithUrlEncodedAuth, -} from '@sentry/core'; - -const client = getCurrentHub().getClient(); -const dsn = client.getDsn(); -const options = client.getOptions(); -const envelopeEndpoint = getEnvelopeEndpointWithUrlEncodedAuth(dsn, options.tunnel); - -// Before: -import { API } from '@sentry/core'; - -const api = new API(dsn, metadata, tunnel); -const dsn = api.getDsn(); -const storeEndpoint = api.getStoreEndpointWithUrlEncodedAuth(); -const envelopeEndpoint = api.getEnvelopeEndpointWithUrlEncodedAuth(); -``` - -## Transport Changes - -The `Transport` API was simplified and some functionality (e.g. APIDetails and client reports) was refactored and moved -to the Client. To send data to Sentry, we switched from the previously used -[Store endpoint](https://develop.sentry.dev/sdk/store/) to the -[Envelopes endpoint](https://develop.sentry.dev/sdk/envelopes/). - -This example shows the new v7 and the v6 Transport API: - -```js -// New in v7: -export interface Transport { - /* Sends an envelope to the Envelope endpoint in Sentry */ - send(request: Envelope): PromiseLike; - /* Waits for all events to be sent or the timeout to expire, whichever comes first */ - flush(timeout?: number): PromiseLike; -} - -// Before: -export interface Transport { - /* Sends the event to the Store endpoint in Sentry */ - sendEvent(event: Event): PromiseLike; - /* Sends the session to the Envelope endpoint in Sentry */ - sendSession?(session: Session | SessionAggregates): PromiseLike; - /* Waits for all events to be sent or the timeout to expire, whichever comes first */ - close(timeout?: number): PromiseLike; - /* Increment the counter for the specific client outcome */ - recordLostEvent?(type: Outcome, category: SentryRequestType): void; -} -``` - -### Custom Transports - -If you rely on a custom transport, you will need to make some adjustments to how it is created when migrating to v7. -Note that we changed our transports from a class-based to a functional approach, meaning that the previously class-based -transports are now created via functions. This also means that custom transports are now passed by specifying a factory -function in the `Sentry.init` options object instead passing the custom transport's class. - -The following example shows how to create a custom transport in v7 vs. how it was done in v6: - -```js -// New in v7: -import { BaseTransportOptions, Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/types'; -import { createTransport } from '@sentry/core'; - -export function makeMyCustomTransport(options: BaseTransportOptions): Transport { - function makeRequest(request: TransportRequest): PromiseLike { - // this is where your sending logic goes - const myCustomRequest = { - body: request.body, - url: options.url - }; - // you define how `sendMyCustomRequest` works - return sendMyCustomRequest(myCustomRequest).then(response => ({ - headers: { - 'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'), - 'retry-after': response.headers.get('Retry-After'), - }, - })); - } - - // `createTransport` takes care of rate limiting and flushing - return createTransport(options, makeRequest); -} - -Sentry.init({ - dsn: '...', - transport: makeMyCustomTransport, // this function will be called when the client is initialized - ... -}) - -// Before: -class MyCustomTransport extends BaseTransport { - constructor(options: TransportOptions) { - // initialize your transport here - super(options); - } - - public sendEvent(event: Event): PromiseLike { - // this is where your sending logic goes - // `url` is decoded from dsn in BaseTransport - const myCustomRequest = createMyCustomRequestFromEvent(event, this.url); - return sendMyCustomRequest(myCustomRequest).then(() => resolve({status: 'success'})); - } - - public sendSession(session: Session): PromiseLike {...} - // ... -} - -Sentry.init({ - dsn: '...', - transport: MyCustomTransport, // the constructor was called when the client was initialized - ... -}) -``` - -Overall, the new way of transport creation allows you to create your custom sending implementation without having to -deal with the conversion of events or sessions to envelopes. We recommend calling using the `createTransport` function -from `@sentry/core` as demonstrated in the example above which, besides creating the `Transport` object with your custom -logic, will also take care of rate limiting and flushing. - -For a complete v7 transport implementation, take a look at our -[browser fetch transport](https://github.com/getsentry/sentry-javascript/blob/ebc938a03d6efe7d0c4bbcb47714e84c9a566a9c/packages/browser/src/transports/fetch.ts#L1-L34). - -### Node Transport Changes - -To clean up the options interface, we now require users to pass down transport related options under the -`transportOptions` key. The options that were changed were `caCerts`, `httpProxy`, and `httpsProxy`. In addition, -`httpProxy` and `httpsProxy` were unified to a single option under the `transportOptions` key, `proxy`. - -```ts -// New in v7: -Sentry.init({ - dsn: '...', - transportOptions: { - caCerts: getMyCaCert(), - proxy: 'http://example.com', - }, -}); - -// Before: -Sentry.init({ - dsn: '...', - caCerts: getMyCaCert(), - httpsProxy: 'http://example.com', -}); -``` - -## Enum Changes - -Given that enums have a high bundle-size impact, our long term goal is to eventually remove all enums from the SDK in -favor of string literals. - -### Removed Enums - -- The previously deprecated enum `Status` was removed (see - [#4891](https://github.com/getsentry/sentry-javascript/pull/4891)). -- The previously deprecated internal-only enum `RequestSessionStatus` was removed (see - [#4889](https://github.com/getsentry/sentry-javascript/pull/4889)) in favor of string literals. -- The previously deprecated internal-only enum `SessionStatus` was removed (see - [#4890](https://github.com/getsentry/sentry-javascript/pull/4890)) in favor of string literals. - -### Deprecated Enums - -The two enums `SpanStatus`, and `Severity` remain deprecated, as we decided to limit the number of high-impact breaking -changes in v7. They will be removed in the next major release which is why we strongly recommend moving to the -corresponding string literals. Here's how to adjust [`Severity`](#severity-severitylevel-and-severitylevels) and -[`SpanStatus`](#spanstatus). - -## Session Changes - -Note: These changes are not relevant for the majority of Sentry users but if you are building an SDK on top of the -Javascript SDK, you might need to make some adaptions. The internal `Session` class was refactored and replaced with a -more functional approach in [#5054](https://github.com/getsentry/sentry-javascript/pull/5054). Instead of the class, we -now export a `Session` interface from `@sentry/types` and three utility functions to create and update a `Session` -object from `@sentry/hub`. This short example shows what has changed and how to deal with the new functions: - -```js -// New in v7: -import { makeSession, updateSession, closeSession } from '@sentry/hub'; - -const session = makeSession({ release: 'v1.0' }); -updateSession(session, { environment: 'prod' }); -closeSession(session, 'ok'); - -// Before: -import { Session } from '@sentry/hub'; - -const session = new Session({ release: 'v1.0' }); -session.update({ environment: 'prod' }); -session.close('ok'); -``` - -## Propagation of Baggage Header - -We introduced a new way of propagating tracing and transaction-related information between services. This change adds -the [`baggage` HTTP header](https://www.w3.org/TR/baggage/) to outgoing requests if the instrumentation of requests is -enabled. Since this adds a header to your HTTP requests, you might need to adjust your Server's CORS settings to allow -this additional header. Take a look at the -[Sentry docs](https://docs.sentry.io/platforms/javascript/performance/connect-services/#navigation-and-other-xhr-requests) -for more in-depth instructions what to change. - -## General API Changes - -For our efforts to reduce bundle size of the SDK we had to remove and refactor parts of the package which introduced a -few changes to the API: - -- Remove support for deprecated `@sentry/apm` package. `@sentry/tracing` should be used instead. -- Remove deprecated `user` field from DSN. `publicKey` should be used instead. -- Remove deprecated `whitelistUrls` and `blacklistUrls` options from `Sentry.init`. They have been superseded by - `allowUrls` and `denyUrls` specifically. See - [our docs page on inclusive language](https://develop.sentry.dev/inclusion/) for more details. -- Gatsby SDK: Remove `Sentry` from `window` object. -- Remove deprecated `Status`, `SessionStatus`, and `RequestSessionStatus` enums. These were only part of an internal - API. If you are using these enums, we encourage you to to look at - [b177690d](https://github.com/getsentry/sentry-javascript/commit/b177690d89640aef2587039113c614672c07d2be), - [5fc3147d](https://github.com/getsentry/sentry-javascript/commit/5fc3147dfaaf1a856d5923e4ba409479e87273be), and - [f99bdd16](https://github.com/getsentry/sentry-javascript/commit/f99bdd16539bf6fac14eccf1a974a4988d586b28) to to see - the changes we've made to our code as result. We generally recommend using string literals instead of the removed - enums. -- Remove 'critical' severity. -- Remove deprecated `getActiveDomain` method and `DomainAsCarrier` type from `@sentry/hub`. -- Rename `registerRequestInstrumentation` to `instrumentOutgoingRequests` in `@sentry/tracing`. -- Remove `Backend` and port its functionality into `Client` (see - [#4911](https://github.com/getsentry/sentry-javascript/pull/4911) and - [#4919](https://github.com/getsentry/sentry-javascript/pull/4919)). `Backend` was an unnecessary abstraction which is - not present in other Sentry SDKs. For the sake of reducing complexity, increasing consistency with other Sentry SDKs - and decreasing bundle-size, `Backend` was removed. -- Remove support for Opera browser pre v15. -- Rename `UserAgent` integration to `HttpContext`. (see - [#5027](https://github.com/getsentry/sentry-javascript/pull/5027)) -- Remove `SDK_NAME` export from `@sentry/browser`, `@sentry/node`, `@sentry/tracing` and `@sentry/vue` packages. -- Removed `eventStatusFromHttpCode` to save on bundle size. -- Replace `BrowserTracing` `maxTransactionDuration` option with `finalTimeout` option -- Removed `ignoreSentryErrors` option from AWS lambda SDK. Errors originating from the SDK will now _always_ be caught - internally. -- Removed `Integrations.BrowserTracing` export from `@sentry/nextjs`. Please import `BrowserTracing` from - `@sentry/nextjs` directly. -- Removed static `id` property from `BrowserTracing` integration. -- Removed usage of deprecated `event.stacktrace` field - -## Sentry Angular SDK Changes - -The Sentry Angular SDK (`@sentry/angular`) is now compiled with the Angular compiler (see -[#4641](https://github.com/getsentry/sentry-javascript/pull/4641)). This change was necessary to fix a long-lasting bug -in the SDK (see [#3282](https://github.com/getsentry/sentry-javascript/issues/3282)): `TraceDirective` and `TraceModule` -can now be used again without risking an application compiler error or having to disable AOT compilation. - -### Angular Version Compatibility - -As in v6, we continue to list Angular 10-13 in our peer dependencies, meaning that these are the Angular versions we -officially support. If you are using v7 with Angular <10 in your project and you experience problems, we recommend -staying on the latest 6.x version until you can upgrade your Angular version. As v7 of our SDK is compiled with the -Angular 10 compiler and we upgraded our Typescript version, the SDK will work with Angular 10 and above. Tests have -shown that Angular 9 seems to work as well (use at your own risk) but we recommend upgrading to a more recent Angular -version. - -### Import Changes - -Due to the compiler change, our NPM package structure changed as well as it now conforms to the -[Angular Package Format v10](https://docs.google.com/document/d/1uh2D6XqaGh2yjjXwfF4SrJqWl1MBhMPntlNBBsk6rbw/edit). In -case you're importing from specific paths other than `@sentry/angular` you will have to adjust these paths. As an -example, `import ... from '@sentry/angular/esm/injex.js'` should be changed to -`import ... from '@sentry/angular/esm2015/index.js'`. Generally, we strongly recommend only importing from -`@sentry/angular`. - -# Upgrading from 6.17.x to 6.18.0 - -Version 6.18.0 deprecates the `frameContextLines` top-level option for the Node SDK. This option will be removed in an -upcoming major version. To migrate off of the top-level option, pass it instead to the new `ContextLines` integration. - -```js -// New in 6.18.0 -init({ - dsn: '__DSN__', - integrations: [new ContextLines({ frameContextLines: 10 })], -}); - -// Before: -init({ - dsn: '__DSN__', - frameContextLines: 10, -}); -``` - -# Upgrading from 6.x to 6.17.x - -You only need to make changes when migrating to `6.17.x` if you are using our internal `Dsn` class. Our internal API -class and typescript enums were deprecated, so we recommend you migrate them as well. - -The internal `Dsn` class was removed in `6.17.0`. For additional details, you can look at the -[PR where this change happened](https://github.com/getsentry/sentry-javascript/pull/4325). To migrate, see the following -example. - -```js -// New in 6.17.0: -import { dsnToString, makeDsn } from '@sentry/utils'; - -const dsn = makeDsn(process.env.SENTRY_DSN); -console.log(dsnToString(dsn)); - -// Before: -import { Dsn } from '@sentry/utils'; - -const dsn = new Dsn(process.env.SENTRY_DSN); -console.log(dsn.toString()); -``` - -The internal API class was deprecated, and will be removed in the next major release. More details can be found in the -[PR that made this change](https://github.com/getsentry/sentry-javascript/pull/4281). To migrate, see the following -example. - -```js -// New in 6.17.0: -import { - initAPIDetails, - getEnvelopeEndpointWithUrlEncodedAuth, - getStoreEndpointWithUrlEncodedAuth, -} from '@sentry/core'; - -const dsn = initAPIDetails(dsn, metadata, tunnel); -const dsn = api.dsn; -const storeEndpoint = getStoreEndpointWithUrlEncodedAuth(api.dsn); -const envelopeEndpoint = getEnvelopeEndpointWithUrlEncodedAuth(api.dsn, api.tunnel); - -// Before: -import { API } from '@sentry/core'; - -const api = new API(dsn, metadata, tunnel); -const dsn = api.getDsn(); -const storeEndpoint = api.getStoreEndpointWithUrlEncodedAuth(); -const envelopeEndpoint = api.getEnvelopeEndpointWithUrlEncodedAuth(); -``` - -## Enum changes - -The enums `Status`, `SpanStatus`, and `Severity` were deprecated, and we've detailed how to migrate away from them -below. We also deprecated the `TransactionMethod`, `Outcome` and `RequestSessionStatus` enums, but those are -internal-only APIs. If you are using them, we encourage you to take a look at the corresponding PRs to see how we've -changed our code as a result. - -- `TransactionMethod`: https://github.com/getsentry/sentry-javascript/pull/4314 -- `Outcome`: https://github.com/getsentry/sentry-javascript/pull/4315 -- `RequestSessionStatus`: https://github.com/getsentry/sentry-javascript/pull/4316 - -#### Status - -We deprecated the `Status` enum in `@sentry/types` and it will be removed in the next major release. We recommend using -string literals to save on bundle size. [PR](https://github.com/getsentry/sentry-javascript/pull/4298). We also removed -the `Status.fromHttpCode` method. This was done to save on bundle size. - -```js -// New in 6.17.0: -import { eventStatusFromHttpCode } from '@sentry/utils'; - -const status = eventStatusFromHttpCode(500); - -// Before: -import { Status } from '@sentry/types'; - -const status = Status.fromHttpCode(500); -``` - -#### SpanStatus - -We deprecated the `Status` enum in `@sentry/tracing` and it will be removed in the next major release. We recommend -using string literals to save on bundle size. [PR](https://github.com/getsentry/sentry-javascript/pull/4299). We also -removed the `SpanStatus.fromHttpCode` method. This was done to save on bundle size. - -```js -// New in 6.17.0: -import { spanStatusfromHttpCode } from '@sentry/tracing'; - -const status = spanStatusfromHttpCode(403); - -// Before: -import { SpanStatus } from '@sentry/tracing'; - -const status = SpanStatus.fromHttpCode(403); -``` - -#### Severity, SeverityLevel, and SeverityLevels - -We deprecated the `Severity` enum in `@sentry/types` and it will be removed in the next major release. We recommend -using string literals (typed as `SeverityLevel`) to save on bundle size. - -```js -// New in 6.17.5: -import { SeverityLevel } from '@sentry/types'; - -const levelA = "error" as SeverityLevel; - -const levelB: SeverityLevel = "error" - -// Before: -import { Severity, SeverityLevel } from '@sentry/types'; - -const levelA = Severity.error; - -const levelB: SeverityLevel = "error" -``` - -# Upgrading from 4.x to 5.x/6.x - -In this version upgrade, there are a few breaking changes. This guide should help you update your code accordingly. - -## Integrations - -We moved optional integrations into their own package, called `@sentry/integrations`. Also, we made a few default -integrations now optional. This is probably the biggest breaking change regarding the upgrade. - -Integrations that are now opt-in and were default before: - -- Dedupe (responsible for sending the same error only once) -- ExtraErrorData (responsible for doing fancy magic, trying to extract data out of the error object using any - non-standard keys) - -Integrations that were pluggable/optional before, that also live in this package: - -- Angular (browser) -- Debug (browser/node) -- Ember (browser) -- ReportingObserver (browser) -- RewriteFrames (browser/node) -- Transaction (browser/node) -- Vue (browser) - -### How to use `@sentry/integrations`? - -Lets start with the approach if you install `@sentry/browser` / `@sentry/node` with `npm` or `yarn`. - -Given you have a `Vue` application running, in order to use the `Vue` integration you need to do the following: - -With `4.x`: - -```js -import * as Sentry from '@sentry/browser'; - -Sentry.init({ - dsn: '___PUBLIC_DSN___', - integrations: [ - new Sentry.Integrations.Vue({ - Vue, - attachProps: true, - }), - ], -}); -``` - -With `5.x` you need to install `@sentry/integrations` and change the import. - -```js -import * as Sentry from '@sentry/browser'; -import * as Integrations from '@sentry/integrations'; - -Sentry.init({ - dsn: '___PUBLIC_DSN___', - integrations: [ - new Integrations.Vue({ - Vue, - attachProps: true, - }), - ], -}); -``` - -In case you are using the CDN version or the Loader, we provide a standalone file for every integration, you can use it -like this: - -```html - - - - - - - - -``` - -## New Scope functions - -We realized how annoying it is to set a whole object using `setExtra`, so there are now a few new methods on the -`Scope`. - -```typescript -setTags(tags: { [key: string]: string | number | boolean | null | undefined }): this; -setExtras(extras: { [key: string]: any }): this; -clearBreadcrumbs(): this; -``` - -So you can do this now: - -```js -// New in 5.x setExtras -Sentry.withScope(scope => { - scope.setExtras(errorInfo); - Sentry.captureException(error); -}); - -// vs. 4.x -Sentry.withScope(scope => { - Object.keys(errorInfo).forEach(key => { - scope.setExtra(key, errorInfo[key]); - }); - Sentry.captureException(error); -}); -``` - -## Less Async API - -We removed a lot of the internal async code since in certain situations it generated a lot of memory pressure. This -really only affects you if you where either using the `BrowserClient` or `NodeClient` directly. - -So all the `capture*` functions now instead of returning `Promise` return `string | undefined`. `string` in -this case is the `event_id`, in case the event will not be sent because of filtering it will return `undefined`. - -## `close` vs. `flush` - -In `4.x` we had both `close` and `flush` on the `Client` draining the internal queue of events, helpful when you were -using `@sentry/node` on a serverless infrastructure. - -Now `close` and `flush` work similar, with the difference that if you call `close` in addition to returning a `Promise` -that you can await it also **disables** the client so it will not send any future events. - -# Migrating from `raven-js` to `@sentry/browser` - -https://docs.sentry.io/platforms/javascript/#browser-table Here are some examples of how the new SDKs work. Please note -that the API for all JavaScript SDKs is the same. - -#### Installation - -> [Docs](https://docs.sentry.io/platforms/javascript/#connecting-the-sdk-to-sentry) - -_Old_: - -```js -Raven.config('___PUBLIC_DSN___', { - release: '1.3.0', -}).install(); -``` - -_New_: - -```js -Sentry.init({ - dsn: '___PUBLIC_DSN___', - release: '1.3.0', -}); -``` - -#### Set a global tag - -> [Docs](https://docs.sentry.io/platforms/javascript/#tagging-events) - -_Old_: - -```js -Raven.setTagsContext({ key: 'value' }); -``` - -_New_: - -```js -Sentry.setTag('key', 'value'); -``` - -#### Set user context - -_Old_: - -```js -Raven.setUserContext({ - id: '123', - email: 'david@example.com', -}); -``` - -_New_: - -```js -Sentry.setUser({ - id: '123', - email: 'david@example.com', -}); -``` - -#### Capture custom exception - -> A scope must now be sent around a capture to add extra information. -> [Docs](https://docs.sentry.io/platforms/javascript/#unsetting-context) - -_Old_: - -```js -try { - throwingFunction(); -} catch (e) { - Raven.captureException(e, { extra: { debug: false } }); -} -``` - -_New_: - -```js -try { - throwingFunction(); -} catch (e) { - Sentry.withScope(scope => { - scope.setExtra('debug', false); - Sentry.captureException(e); - }); -} -``` - -#### Capture a message - -> A scope must now be sent around a capture to add extra information. -> [Docs](https://docs.sentry.io/platforms/javascript/#unsetting-context) - -_Old_: - -```js -Raven.captureMessage('test1', 'info'); -Raven.captureMessage('test2', 'info', { extra: { debug: false } }); -``` - -_New_: - -```js -Sentry.captureMessage('test1', 'info'); -Sentry.withScope(scope => { - scope.setExtra('debug', false); - Sentry.captureMessage('test2', 'info'); -}); -``` - -#### Breadcrumbs - -> [Docs](https://docs.sentry.io/platforms/javascript/#breadcrumbs) - -_Old_: - -```js -Raven.captureBreadcrumb({ - message: 'Item added to shopping cart', - category: 'action', - data: { - isbn: '978-1617290541', - cartSize: '3', - }, -}); -``` - -_New_: - -```js -Sentry.addBreadcrumb({ - message: 'Item added to shopping cart', - category: 'action', - data: { - isbn: '978-1617290541', - cartSize: '3', - }, -}); -``` - -### Ignoring Urls - -> 'ignoreUrls' was renamed to 'denyUrls'. 'ignoreErrors', which has a similar name was not renamed. -> [Docs](https://docs.sentry.io/error-reporting/configuration/?platform=browser#deny-urls) and -> [Decluttering Sentry](https://docs.sentry.io/platforms/javascript/#decluttering-sentry) - -_Old_: - -```js -Raven.config('___PUBLIC_DSN___', { - ignoreUrls: ['https://www.baddomain.com', /graph\.facebook\.com/i], -}); -``` - -_New_: - -```js -Sentry.init({ - denyUrls: ['https://www.baddomain.com', /graph\.facebook\.com/i], -}); -``` - -### Ignoring Events (`shouldSendCallback`) - -> `shouldSendCallback` was renamed to `beforeSend` -> ([#2253](https://github.com/getsentry/sentry-javascript/issues/2253)). Instead of returning `false`, you must return -> `null` to omit sending the event. -> [Docs](https://docs.sentry.io/error-reporting/configuration/filtering/?platform=browser#before-send) - -_Old_: - -```js -Raven.config('___PUBLIC_DSN___', { - shouldSendCallback(event) { - // Only send events that include user data - if (event.user) { - return true; - } - return false; - }, -}); -``` - -_New_: - -```js -Sentry.init({ - beforeSend(event) { - if (event.user) { - return event; - } - return null; - }, -}); -``` - -### Modifying Events (`dataCallback`) - -_Old_: - -```js -Raven.config('___PUBLIC_DSN___', { - dataCallback(event) { - if (event.user) { - // Don't send user's email address - delete event.user.email; - } - return event; - }, -}); -``` - -_New_: - -```js -Sentry.init({ - beforeSend(event) { - if (event.user) { - delete event.user.email; - } - return event; - }, -}); -``` - -### Attaching Stacktraces - -> 'stacktrace' was renamed to 'attachStacktrace'. -> [Docs](https://docs.sentry.io/error-reporting/configuration/?platform=browser#attach-stacktrace) - -_Old_: - -```js -Raven.config('___PUBLIC_DSN___', { - stacktrace: true, -}); -``` - -_New_: - -```js -Sentry.init({ - attachStacktrace: true, -}); -``` - -### Disabling Promises Handling - -_Old_: - -```js -Raven.config('___PUBLIC_DSN___', { - captureUnhandledRejections: false, -}); -``` - -_New_: - -```js -Sentry.init({ - integrations: [ - new Sentry.Integrations.GlobalHandlers({ - onunhandledrejection: false, - }), - ], -}); -``` diff --git a/docs/migration/v4-to-v5_v6.md b/docs/migration/v4-to-v5_v6.md new file mode 100644 index 000000000000..6928022eef20 --- /dev/null +++ b/docs/migration/v4-to-v5_v6.md @@ -0,0 +1,404 @@ +# Upgrading from 4.x to 5.x/6.x + +We recommend upgrading from `4.x` to `6.x` directly. Migrating from `5.x` to `6.x` has no breaking changes to the SDK's API. + +In this version upgrade, there are a few breaking changes. This guide should help you update your code accordingly. + +## Integrations + +We moved optional integrations into their own package, called `@sentry/integrations`. Also, we made a few default +integrations now optional. This is probably the biggest breaking change regarding the upgrade. + +Integrations that are now opt-in and were default before: + +- Dedupe (responsible for sending the same error only once) +- ExtraErrorData (responsible for doing fancy magic, trying to extract data out of the error object using any + non-standard keys) + +Integrations that were pluggable/optional before, that also live in this package: + +- Angular (browser) +- Debug (browser/node) +- Ember (browser) +- ReportingObserver (browser) +- RewriteFrames (browser/node) +- Transaction (browser/node) +- Vue (browser) + +### How to use `@sentry/integrations`? + +Lets start with the approach if you install `@sentry/browser` / `@sentry/node` with `npm` or `yarn`. + +Given you have a `Vue` application running, in order to use the `Vue` integration you need to do the following: + +With `4.x`: + +```js +import * as Sentry from '@sentry/browser'; + +Sentry.init({ + dsn: '___PUBLIC_DSN___', + integrations: [ + new Sentry.Integrations.Vue({ + Vue, + attachProps: true, + }), + ], +}); +``` + +With `5.x` you need to install `@sentry/integrations` and change the import. + +```js +import * as Sentry from '@sentry/browser'; +import * as Integrations from '@sentry/integrations'; + +Sentry.init({ + dsn: '___PUBLIC_DSN___', + integrations: [ + new Integrations.Vue({ + Vue, + attachProps: true, + }), + ], +}); +``` + +In case you are using the CDN version or the Loader, we provide a standalone file for every integration, you can use it +like this: + +```html + + + + + + + + +``` + +## New Scope functions + +We realized how annoying it is to set a whole object using `setExtra`, so there are now a few new methods on the +`Scope`. + +```typescript +setTags(tags: { [key: string]: string | number | boolean | null | undefined }): this; +setExtras(extras: { [key: string]: any }): this; +clearBreadcrumbs(): this; +``` + +So you can do this now: + +```js +// New in 5.x setExtras +Sentry.withScope(scope => { + scope.setExtras(errorInfo); + Sentry.captureException(error); +}); + +// vs. 4.x +Sentry.withScope(scope => { + Object.keys(errorInfo).forEach(key => { + scope.setExtra(key, errorInfo[key]); + }); + Sentry.captureException(error); +}); +``` + +## Less Async API + +We removed a lot of the internal async code since in certain situations it generated a lot of memory pressure. This +really only affects you if you where either using the `BrowserClient` or `NodeClient` directly. + +So all the `capture*` functions now instead of returning `Promise` return `string | undefined`. `string` in +this case is the `event_id`, in case the event will not be sent because of filtering it will return `undefined`. + +## `close` vs. `flush` + +In `4.x` we had both `close` and `flush` on the `Client` draining the internal queue of events, helpful when you were +using `@sentry/node` on a serverless infrastructure. + +Now `close` and `flush` work similar, with the difference that if you call `close` in addition to returning a `Promise` +that you can await it also **disables** the client so it will not send any future events. + +# Migrating from `raven-js` to `@sentry/browser` + +https://docs.sentry.io/platforms/javascript/#browser-table Here are some examples of how the new SDKs work. Please note +that the API for all JavaScript SDKs is the same. + +#### Installation + +> [Docs](https://docs.sentry.io/platforms/javascript/#connecting-the-sdk-to-sentry) + +_Old_: + +```js +Raven.config('___PUBLIC_DSN___', { + release: '1.3.0', +}).install(); +``` + +_New_: + +```js +Sentry.init({ + dsn: '___PUBLIC_DSN___', + release: '1.3.0', +}); +``` + +#### Set a global tag + +> [Docs](https://docs.sentry.io/platforms/javascript/#tagging-events) + +_Old_: + +```js +Raven.setTagsContext({ key: 'value' }); +``` + +_New_: + +```js +Sentry.setTag('key', 'value'); +``` + +#### Set user context + +_Old_: + +```js +Raven.setUserContext({ + id: '123', + email: 'david@example.com', +}); +``` + +_New_: + +```js +Sentry.setUser({ + id: '123', + email: 'david@example.com', +}); +``` + +#### Capture custom exception + +> A scope must now be sent around a capture to add extra information. +> [Docs](https://docs.sentry.io/platforms/javascript/#unsetting-context) + +_Old_: + +```js +try { + throwingFunction(); +} catch (e) { + Raven.captureException(e, { extra: { debug: false } }); +} +``` + +_New_: + +```js +try { + throwingFunction(); +} catch (e) { + Sentry.withScope(scope => { + scope.setExtra('debug', false); + Sentry.captureException(e); + }); +} +``` + +#### Capture a message + +> A scope must now be sent around a capture to add extra information. +> [Docs](https://docs.sentry.io/platforms/javascript/#unsetting-context) + +_Old_: + +```js +Raven.captureMessage('test1', 'info'); +Raven.captureMessage('test2', 'info', { extra: { debug: false } }); +``` + +_New_: + +```js +Sentry.captureMessage('test1', 'info'); +Sentry.withScope(scope => { + scope.setExtra('debug', false); + Sentry.captureMessage('test2', 'info'); +}); +``` + +#### Breadcrumbs + +> [Docs](https://docs.sentry.io/platforms/javascript/#breadcrumbs) + +_Old_: + +```js +Raven.captureBreadcrumb({ + message: 'Item added to shopping cart', + category: 'action', + data: { + isbn: '978-1617290541', + cartSize: '3', + }, +}); +``` + +_New_: + +```js +Sentry.addBreadcrumb({ + message: 'Item added to shopping cart', + category: 'action', + data: { + isbn: '978-1617290541', + cartSize: '3', + }, +}); +``` + +### Ignoring Urls + +> 'ignoreUrls' was renamed to 'denyUrls'. 'ignoreErrors', which has a similar name was not renamed. +> [Docs](https://docs.sentry.io/error-reporting/configuration/?platform=browser#deny-urls) and +> [Decluttering Sentry](https://docs.sentry.io/platforms/javascript/#decluttering-sentry) + +_Old_: + +```js +Raven.config('___PUBLIC_DSN___', { + ignoreUrls: ['https://www.baddomain.com', /graph\.facebook\.com/i], +}); +``` + +_New_: + +```js +Sentry.init({ + denyUrls: ['https://www.baddomain.com', /graph\.facebook\.com/i], +}); +``` + +### Ignoring Events (`shouldSendCallback`) + +> `shouldSendCallback` was renamed to `beforeSend` +> ([#2253](https://github.com/getsentry/sentry-javascript/issues/2253)). Instead of returning `false`, you must return +> `null` to omit sending the event. +> [Docs](https://docs.sentry.io/error-reporting/configuration/filtering/?platform=browser#before-send) + +_Old_: + +```js +Raven.config('___PUBLIC_DSN___', { + shouldSendCallback(event) { + // Only send events that include user data + if (event.user) { + return true; + } + return false; + }, +}); +``` + +_New_: + +```js +Sentry.init({ + beforeSend(event) { + if (event.user) { + return event; + } + return null; + }, +}); +``` + +### Modifying Events (`dataCallback`) + +_Old_: + +```js +Raven.config('___PUBLIC_DSN___', { + dataCallback(event) { + if (event.user) { + // Don't send user's email address + delete event.user.email; + } + return event; + }, +}); +``` + +_New_: + +```js +Sentry.init({ + beforeSend(event) { + if (event.user) { + delete event.user.email; + } + return event; + }, +}); +``` + +### Attaching Stacktraces + +> 'stacktrace' was renamed to 'attachStacktrace'. +> [Docs](https://docs.sentry.io/error-reporting/configuration/?platform=browser#attach-stacktrace) + +_Old_: + +```js +Raven.config('___PUBLIC_DSN___', { + stacktrace: true, +}); +``` + +_New_: + +```js +Sentry.init({ + attachStacktrace: true, +}); +``` + +### Disabling Promises Handling + +_Old_: + +```js +Raven.config('___PUBLIC_DSN___', { + captureUnhandledRejections: false, +}); +``` + +_New_: + +```js +Sentry.init({ + integrations: [ + new Sentry.Integrations.GlobalHandlers({ + onunhandledrejection: false, + }), + ], +}); +``` diff --git a/docs/migration/v6-to-v7.md b/docs/migration/v6-to-v7.md new file mode 100644 index 000000000000..ac6ce0519e8e --- /dev/null +++ b/docs/migration/v6-to-v7.md @@ -0,0 +1,544 @@ +# Upgrading from 6.x to 7.x + +The v7 version of the JavaScript SDK requires a self-hosted version of Sentry 20.6.0 or higher. + +The main goal of version 7 is to reduce bundle size. This version is breaking because we removed deprecated APIs, +upgraded our build tooling, and restructured npm package contents. Below we will outline all the breaking changes you +should consider when upgrading. + +**TL;DR** If you only use basic features of Sentry, or you simply copy & pasted the setup examples from our docs, here's +what changed for you: + +- If you installed additional Sentry packages, such as`@sentry/tracing` alongside your Sentry SDK (e.g. `@sentry/react` + or `@sentry/node`), make sure to upgrade all of them to version 7. +- Our CDN bundles are now ES6 - you will need to [reconfigure your script tags](#renaming-of-cdn-bundles) if you want to + keep supporting ES5 and IE11 on the new SDK version. +- Distributed CommonJS files will be ES6. Use a transpiler if you need to support old node versions. +- We bumped the TypeScript version we generate our types with to 3.8.3. Please check if your TypeScript projects using + TypeScript version 3.7 or lower still compile. Otherwise, upgrade your TypeScript version. +- `whitelistUrls` and `blacklistUrls` have been renamed to `allowUrls` and `denyUrls` in the `Sentry.init()` options. +- The `UserAgent` integration is now called `HttpContext`. +- If you are using Performance Monitoring and with tracing enabled, you might have to + [make adjustments to your server's CORS settings](#propagation-of-baggage-header) + +## Dropping Support for Node.js v6 + +Node.js version 6 has reached end of life in April 2019. For Sentry JavaScript SDK version 7, we will no longer be +supporting version 6 of Node.js. + +As far as SDK development goes, dropping support means no longer running integration tests for Node.js version 6, and +also no longer handling edge cases specific to version 6. Running the new SDK version on Node.js v6 is therefore highly +discouraged. + +## Removal of `@sentry/minimal` + +The `@sentry/minimal` package was deleted and it's functionality was moved to `@sentry/hub`. All exports from +`@sentry/minimal` should be available in `@sentry/hub` other than `_callOnClient` function which was removed. + +```ts +// New in v7: +import { addBreadcrumb, captureException, configureScope, setTag } from '@sentry/hub'; + +// Before: +import { addBreadcrumb, captureException, configureScope, setTag } from '@sentry/minimal'; +``` + +## Explicit Client Options + +In v7, we've updated the `Client` to have options separate from the options passed into `Sentry.init`. This means that +constructing a client now requires 3 options: `integrations`, `transport` and `stackParser`. These can be customized as +you see fit. + +```ts +import { BrowserClient, defaultStackParser, defaultIntegrations, makeFetchTransport } from '@sentry/browser'; + +// New in v7: +const client = new BrowserClient({ + transport: makeFetchTransport, + stackParser: defaultStackParser, + integrations: defaultIntegrations, +}); + +// Before: +const client = new BrowserClient(); +``` + +Since you now explicitly pass in the dependencies of the client, you can also tree-shake out dependencies that you do +not use this way. For example, you can tree-shake out the SDK's default integrations and only use the ones that you want +like so: + +```ts +import { + BrowserClient, + Breadcrumbs, + Dedupe, + defaultStackParser, + GlobalHandlers, + Integrations, + makeFetchTransport, + LinkedErrors, +} from '@sentry/browser'; + +// New in v7: +const client = new BrowserClient({ + transport: makeFetchTransport, + stackParser: defaultStackParser, + integrations: [new Breadcrumbs(), new GlobalHandlers(), new LinkedErrors(), new Dedupe()], +}); +``` + +## Removal Of Old Platform Integrations From `@sentry/integrations` Package + +The following classes will be removed from the `@sentry/integrations` package and can no longer be used: + +- `Angular` +- `Ember` +- `Vue` + +These classes have been superseded and were moved into their own packages, `@sentry/angular`, `@sentry/ember`, and +`@sentry/vue` in a previous version. Refer to those packages if you want to integrate Sentry into your Angular, Ember, +or Vue application. + +## Moving To ES6 For CommonJS Files + +From version 7 onwards, the CommonJS files in Sentry JavaScript SDK packages will use ES6. + +If you need to support Internet Explorer 11 or old Node.js versions, we recommend using a preprocessing tool like +[Babel](https://babeljs.io/) to convert Sentry packages to ES5. + +## Renaming Of CDN Bundles + +CDN bundles will be ES6 by default. Files that followed the naming scheme `bundle.es6.min.js` were renamed to +`bundle.min.js` and any bundles using ES5 (files without `.es6`) turned into `bundle.es5.min.js`. + +See our [docs on CDN bundles](https://docs.sentry.io/platforms/javascript/install/cdn/) for more information. + +## Restructuring Of Package Content + +Up until v6.x, we have published our packages on npm with the following structure: + +- `build` folder contained CDN bundles +- `dist` folder contained CommonJS files and TypeScript declarations +- `esm` folder contained ESM files and TypeScript declarations + +Moving forward the JavaScript SDK packages will generally have the following structure: + +- `cjs` folder contains CommonJS files +- `esm` folder contains ESM files +- `types` folder contains TypeScript declarations + +**CDN bundles of version 7 or higher will no longer be distributed through our npm package.** This means that most +third-party CDNs like [unpkg](https://unpkg.com/) or [jsDelivr](https://www.jsdelivr.com/) will also not provide them. + +If you depend on any specific files in a Sentry JavaScript npm package, you will most likely need to update their +references. For example, imports on `@sentry/browser/dist/client` will become `@sentry/browser/cjs/client`. However, +directly importing from specific files is discouraged. + +## Removing the `API` class from `@sentry/core` + +The internal `API` class was removed in favor of using client options explicitly. + +```js +// New in v7: +import { + initAPIDetails, + getEnvelopeEndpointWithUrlEncodedAuth, + getStoreEndpointWithUrlEncodedAuth, +} from '@sentry/core'; + +const client = getCurrentHub().getClient(); +const dsn = client.getDsn(); +const options = client.getOptions(); +const envelopeEndpoint = getEnvelopeEndpointWithUrlEncodedAuth(dsn, options.tunnel); + +// Before: +import { API } from '@sentry/core'; + +const api = new API(dsn, metadata, tunnel); +const dsn = api.getDsn(); +const storeEndpoint = api.getStoreEndpointWithUrlEncodedAuth(); +const envelopeEndpoint = api.getEnvelopeEndpointWithUrlEncodedAuth(); +``` + +## Transport Changes + +The `Transport` API was simplified and some functionality (e.g. APIDetails and client reports) was refactored and moved +to the Client. To send data to Sentry, we switched from the previously used +[Store endpoint](https://develop.sentry.dev/sdk/store/) to the +[Envelopes endpoint](https://develop.sentry.dev/sdk/envelopes/). + +This example shows the new v7 and the v6 Transport API: + +```js +// New in v7: +export interface Transport { + /* Sends an envelope to the Envelope endpoint in Sentry */ + send(request: Envelope): PromiseLike; + /* Waits for all events to be sent or the timeout to expire, whichever comes first */ + flush(timeout?: number): PromiseLike; +} + +// Before: +export interface Transport { + /* Sends the event to the Store endpoint in Sentry */ + sendEvent(event: Event): PromiseLike; + /* Sends the session to the Envelope endpoint in Sentry */ + sendSession?(session: Session | SessionAggregates): PromiseLike; + /* Waits for all events to be sent or the timeout to expire, whichever comes first */ + close(timeout?: number): PromiseLike; + /* Increment the counter for the specific client outcome */ + recordLostEvent?(type: Outcome, category: SentryRequestType): void; +} +``` + +### Custom Transports + +If you rely on a custom transport, you will need to make some adjustments to how it is created when migrating to v7. +Note that we changed our transports from a class-based to a functional approach, meaning that the previously class-based +transports are now created via functions. This also means that custom transports are now passed by specifying a factory +function in the `Sentry.init` options object instead passing the custom transport's class. + +The following example shows how to create a custom transport in v7 vs. how it was done in v6: + +```js +// New in v7: +import { BaseTransportOptions, Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/types'; +import { createTransport } from '@sentry/core'; + +export function makeMyCustomTransport(options: BaseTransportOptions): Transport { + function makeRequest(request: TransportRequest): PromiseLike { + // this is where your sending logic goes + const myCustomRequest = { + body: request.body, + url: options.url + }; + // you define how `sendMyCustomRequest` works + return sendMyCustomRequest(myCustomRequest).then(response => ({ + headers: { + 'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'), + 'retry-after': response.headers.get('Retry-After'), + }, + })); + } + + // `createTransport` takes care of rate limiting and flushing + return createTransport(options, makeRequest); +} + +Sentry.init({ + dsn: '...', + transport: makeMyCustomTransport, // this function will be called when the client is initialized + ... +}) + +// Before: +class MyCustomTransport extends BaseTransport { + constructor(options: TransportOptions) { + // initialize your transport here + super(options); + } + + public sendEvent(event: Event): PromiseLike { + // this is where your sending logic goes + // `url` is decoded from dsn in BaseTransport + const myCustomRequest = createMyCustomRequestFromEvent(event, this.url); + return sendMyCustomRequest(myCustomRequest).then(() => resolve({status: 'success'})); + } + + public sendSession(session: Session): PromiseLike {...} + // ... +} + +Sentry.init({ + dsn: '...', + transport: MyCustomTransport, // the constructor was called when the client was initialized + ... +}) +``` + +Overall, the new way of transport creation allows you to create your custom sending implementation without having to +deal with the conversion of events or sessions to envelopes. We recommend calling using the `createTransport` function +from `@sentry/core` as demonstrated in the example above which, besides creating the `Transport` object with your custom +logic, will also take care of rate limiting and flushing. + +For a complete v7 transport implementation, take a look at our +[browser fetch transport](https://github.com/getsentry/sentry-javascript/blob/ebc938a03d6efe7d0c4bbcb47714e84c9a566a9c/packages/browser/src/transports/fetch.ts#L1-L34). + +### Node Transport Changes + +To clean up the options interface, we now require users to pass down transport related options under the +`transportOptions` key. The options that were changed were `caCerts`, `httpProxy`, and `httpsProxy`. In addition, +`httpProxy` and `httpsProxy` were unified to a single option under the `transportOptions` key, `proxy`. + +```ts +// New in v7: +Sentry.init({ + dsn: '...', + transportOptions: { + caCerts: getMyCaCert(), + proxy: 'http://example.com', + }, +}); + +// Before: +Sentry.init({ + dsn: '...', + caCerts: getMyCaCert(), + httpsProxy: 'http://example.com', +}); +``` + +## Enum Changes + +Given that enums have a high bundle-size impact, our long term goal is to eventually remove all enums from the SDK in +favor of string literals. + +### Removed Enums + +- The previously deprecated enum `Status` was removed (see + [#4891](https://github.com/getsentry/sentry-javascript/pull/4891)). +- The previously deprecated internal-only enum `RequestSessionStatus` was removed (see + [#4889](https://github.com/getsentry/sentry-javascript/pull/4889)) in favor of string literals. +- The previously deprecated internal-only enum `SessionStatus` was removed (see + [#4890](https://github.com/getsentry/sentry-javascript/pull/4890)) in favor of string literals. + +### Deprecated Enums + +The two enums `SpanStatus`, and `Severity` remain deprecated, as we decided to limit the number of high-impact breaking +changes in v7. They will be removed in the next major release which is why we strongly recommend moving to the +corresponding string literals. Here's how to adjust [`Severity`](#severity-severitylevel-and-severitylevels) and +[`SpanStatus`](#spanstatus). + +## Session Changes + +Note: These changes are not relevant for the majority of Sentry users but if you are building an SDK on top of the +Javascript SDK, you might need to make some adaptions. The internal `Session` class was refactored and replaced with a +more functional approach in [#5054](https://github.com/getsentry/sentry-javascript/pull/5054). Instead of the class, we +now export a `Session` interface from `@sentry/types` and three utility functions to create and update a `Session` +object from `@sentry/hub`. This short example shows what has changed and how to deal with the new functions: + +```js +// New in v7: +import { makeSession, updateSession, closeSession } from '@sentry/hub'; + +const session = makeSession({ release: 'v1.0' }); +updateSession(session, { environment: 'prod' }); +closeSession(session, 'ok'); + +// Before: +import { Session } from '@sentry/hub'; + +const session = new Session({ release: 'v1.0' }); +session.update({ environment: 'prod' }); +session.close('ok'); +``` + +## Propagation of Baggage Header + +We introduced a new way of propagating tracing and transaction-related information between services. This change adds +the [`baggage` HTTP header](https://www.w3.org/TR/baggage/) to outgoing requests if the instrumentation of requests is +enabled. Since this adds a header to your HTTP requests, you might need to adjust your Server's CORS settings to allow +this additional header. Take a look at the +[Sentry docs](https://docs.sentry.io/platforms/javascript/performance/connect-services/#navigation-and-other-xhr-requests) +for more in-depth instructions what to change. + +## General API Changes + +For our efforts to reduce bundle size of the SDK we had to remove and refactor parts of the package which introduced a +few changes to the API: + +- Remove support for deprecated `@sentry/apm` package. `@sentry/tracing` should be used instead. +- Remove deprecated `user` field from DSN. `publicKey` should be used instead. +- Remove deprecated `whitelistUrls` and `blacklistUrls` options from `Sentry.init`. They have been superseded by + `allowUrls` and `denyUrls` specifically. See + [our docs page on inclusive language](https://develop.sentry.dev/inclusion/) for more details. +- Gatsby SDK: Remove `Sentry` from `window` object. +- Remove deprecated `Status`, `SessionStatus`, and `RequestSessionStatus` enums. These were only part of an internal + API. If you are using these enums, we encourage you to to look at + [b177690d](https://github.com/getsentry/sentry-javascript/commit/b177690d89640aef2587039113c614672c07d2be), + [5fc3147d](https://github.com/getsentry/sentry-javascript/commit/5fc3147dfaaf1a856d5923e4ba409479e87273be), and + [f99bdd16](https://github.com/getsentry/sentry-javascript/commit/f99bdd16539bf6fac14eccf1a974a4988d586b28) to to see + the changes we've made to our code as result. We generally recommend using string literals instead of the removed + enums. +- Remove 'critical' severity. +- Remove deprecated `getActiveDomain` method and `DomainAsCarrier` type from `@sentry/hub`. +- Rename `registerRequestInstrumentation` to `instrumentOutgoingRequests` in `@sentry/tracing`. +- Remove `Backend` and port its functionality into `Client` (see + [#4911](https://github.com/getsentry/sentry-javascript/pull/4911) and + [#4919](https://github.com/getsentry/sentry-javascript/pull/4919)). `Backend` was an unnecessary abstraction which is + not present in other Sentry SDKs. For the sake of reducing complexity, increasing consistency with other Sentry SDKs + and decreasing bundle-size, `Backend` was removed. +- Remove support for Opera browser pre v15. +- Rename `UserAgent` integration to `HttpContext`. (see + [#5027](https://github.com/getsentry/sentry-javascript/pull/5027)) +- Remove `SDK_NAME` export from `@sentry/browser`, `@sentry/node`, `@sentry/tracing` and `@sentry/vue` packages. +- Removed `eventStatusFromHttpCode` to save on bundle size. +- Replace `BrowserTracing` `maxTransactionDuration` option with `finalTimeout` option +- Removed `ignoreSentryErrors` option from AWS lambda SDK. Errors originating from the SDK will now _always_ be caught + internally. +- Removed `Integrations.BrowserTracing` export from `@sentry/nextjs`. Please import `BrowserTracing` from + `@sentry/nextjs` directly. +- Removed static `id` property from `BrowserTracing` integration. +- Removed usage of deprecated `event.stacktrace` field + +## Sentry Angular SDK Changes + +The Sentry Angular SDK (`@sentry/angular`) is now compiled with the Angular compiler (see +[#4641](https://github.com/getsentry/sentry-javascript/pull/4641)). This change was necessary to fix a long-lasting bug +in the SDK (see [#3282](https://github.com/getsentry/sentry-javascript/issues/3282)): `TraceDirective` and `TraceModule` +can now be used again without risking an application compiler error or having to disable AOT compilation. + +### Angular Version Compatibility + +As in v6, we continue to list Angular 10-13 in our peer dependencies, meaning that these are the Angular versions we +officially support. If you are using v7 with Angular <10 in your project and you experience problems, we recommend +staying on the latest 6.x version until you can upgrade your Angular version. As v7 of our SDK is compiled with the +Angular 10 compiler and we upgraded our Typescript version, the SDK will work with Angular 10 and above. Tests have +shown that Angular 9 seems to work as well (use at your own risk) but we recommend upgrading to a more recent Angular +version. + +### Import Changes + +Due to the compiler change, our NPM package structure changed as well as it now conforms to the +[Angular Package Format v10](https://docs.google.com/document/d/1uh2D6XqaGh2yjjXwfF4SrJqWl1MBhMPntlNBBsk6rbw/edit). In +case you're importing from specific paths other than `@sentry/angular` you will have to adjust these paths. As an +example, `import ... from '@sentry/angular/esm/injex.js'` should be changed to +`import ... from '@sentry/angular/esm2015/index.js'`. Generally, we strongly recommend only importing from +`@sentry/angular`. + +# Upgrading from 6.17.x to 6.18.0 + +Version 6.18.0 deprecates the `frameContextLines` top-level option for the Node SDK. This option will be removed in an +upcoming major version. To migrate off of the top-level option, pass it instead to the new `ContextLines` integration. + +```js +// New in 6.18.0 +init({ + dsn: '__DSN__', + integrations: [new ContextLines({ frameContextLines: 10 })], +}); + +// Before: +init({ + dsn: '__DSN__', + frameContextLines: 10, +}); +``` + +# Upgrading from 6.x to 6.17.x + +You only need to make changes when migrating to `6.17.x` if you are using our internal `Dsn` class. Our internal API +class and typescript enums were deprecated, so we recommend you migrate them as well. + +The internal `Dsn` class was removed in `6.17.0`. For additional details, you can look at the +[PR where this change happened](https://github.com/getsentry/sentry-javascript/pull/4325). To migrate, see the following +example. + +```js +// New in 6.17.0: +import { dsnToString, makeDsn } from '@sentry/utils'; + +const dsn = makeDsn(process.env.SENTRY_DSN); +console.log(dsnToString(dsn)); + +// Before: +import { Dsn } from '@sentry/utils'; + +const dsn = new Dsn(process.env.SENTRY_DSN); +console.log(dsn.toString()); +``` + +The internal API class was deprecated, and will be removed in the next major release. More details can be found in the +[PR that made this change](https://github.com/getsentry/sentry-javascript/pull/4281). To migrate, see the following +example. + +```js +// New in 6.17.0: +import { + initAPIDetails, + getEnvelopeEndpointWithUrlEncodedAuth, + getStoreEndpointWithUrlEncodedAuth, +} from '@sentry/core'; + +const dsn = initAPIDetails(dsn, metadata, tunnel); +const dsn = api.dsn; +const storeEndpoint = getStoreEndpointWithUrlEncodedAuth(api.dsn); +const envelopeEndpoint = getEnvelopeEndpointWithUrlEncodedAuth(api.dsn, api.tunnel); + +// Before: +import { API } from '@sentry/core'; + +const api = new API(dsn, metadata, tunnel); +const dsn = api.getDsn(); +const storeEndpoint = api.getStoreEndpointWithUrlEncodedAuth(); +const envelopeEndpoint = api.getEnvelopeEndpointWithUrlEncodedAuth(); +``` + +## Enum changes + +The enums `Status`, `SpanStatus`, and `Severity` were deprecated, and we've detailed how to migrate away from them +below. We also deprecated the `TransactionMethod`, `Outcome` and `RequestSessionStatus` enums, but those are +internal-only APIs. If you are using them, we encourage you to take a look at the corresponding PRs to see how we've +changed our code as a result. + +- `TransactionMethod`: https://github.com/getsentry/sentry-javascript/pull/4314 +- `Outcome`: https://github.com/getsentry/sentry-javascript/pull/4315 +- `RequestSessionStatus`: https://github.com/getsentry/sentry-javascript/pull/4316 + +#### Status + +We deprecated the `Status` enum in `@sentry/types` and it will be removed in the next major release. We recommend using +string literals to save on bundle size. [PR](https://github.com/getsentry/sentry-javascript/pull/4298). We also removed +the `Status.fromHttpCode` method. This was done to save on bundle size. + +```js +// New in 6.17.0: +import { eventStatusFromHttpCode } from '@sentry/utils'; + +const status = eventStatusFromHttpCode(500); + +// Before: +import { Status } from '@sentry/types'; + +const status = Status.fromHttpCode(500); +``` + +#### SpanStatus + +We deprecated the `Status` enum in `@sentry/tracing` and it will be removed in the next major release. We recommend +using string literals to save on bundle size. [PR](https://github.com/getsentry/sentry-javascript/pull/4299). We also +removed the `SpanStatus.fromHttpCode` method. This was done to save on bundle size. + +```js +// New in 6.17.0: +import { spanStatusfromHttpCode } from '@sentry/tracing'; + +const status = spanStatusfromHttpCode(403); + +// Before: +import { SpanStatus } from '@sentry/tracing'; + +const status = SpanStatus.fromHttpCode(403); +``` + +#### Severity, SeverityLevel, and SeverityLevels + +We deprecated the `Severity` enum in `@sentry/types` and it will be removed in the next major release. We recommend +using string literals (typed as `SeverityLevel`) to save on bundle size. + +```js +// New in 6.17.5: +import { SeverityLevel } from '@sentry/types'; + +const levelA = "error" as SeverityLevel; + +const levelB: SeverityLevel = "error" + +// Before: +import { Severity, SeverityLevel } from '@sentry/types'; + +const levelA = Severity.error; + +const levelB: SeverityLevel = "error" +``` + From 006b096ac5e16bbb9ff74a01023821113e46fe8e Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Thu, 29 Feb 2024 07:42:16 -0800 Subject: [PATCH 06/17] fix(processing): Only mark aggregate errors as exception groups (#10850) When Sentry started supporting the idea of exception groups, two changes happened. In the SDK, we adapted our logic for handling linked errors to also handle `AggregateError`s. And in our ingest pipeline, we began looking for an `is_exception_group` flag on the last entry in `event.exception.values; when we found it, we'd then ignore that entry when grouping and titling events, under the assumption that it was just a container and therefore wasn't meaningful. When it came to instances of `AggregateError`, this worked great. For linked errors, however, this caused us to focus on the `cause` error rather than the error which was actually caught, with the result that it both threw off grouping and made for some very unhelpful titling of issues. (See the screenshot below, in which the first three errors are, respectively, an `UndefinedResponseBodyError`, a `RequestError`, and an `InternalServerError`, though you'd be hard pressed to figure that out without opening them up.) This fixes those problems by restricting the use of the `is_exception_group` flag to `AggregateError`s. Note: In order to update the tests to work with this change, I had add in consideration of the error `name` property and the corresponding event `type` property, to match what we do in real life. To keep things readable, there's a new mock `AggregateError` class, which I adapted all the tests to use. --- packages/utils/src/aggregate-errors.ts | 3 +- packages/utils/test/aggregate-errors.test.ts | 51 ++++++++++++++------ 2 files changed, 38 insertions(+), 16 deletions(-) diff --git a/packages/utils/src/aggregate-errors.ts b/packages/utils/src/aggregate-errors.ts index 4547203b4fd2..864956ad4716 100644 --- a/packages/utils/src/aggregate-errors.ts +++ b/packages/utils/src/aggregate-errors.ts @@ -57,6 +57,7 @@ function aggregateExceptionsFromError( let newExceptions = [...prevExceptions]; + // Recursively call this function in order to walk down a chain of errors if (isInstanceOf(error[key], Error)) { applyExceptionGroupFieldsForParentException(exception, exceptionId); const newException = exceptionFromErrorImplementation(parser, error[key]); @@ -106,7 +107,7 @@ function applyExceptionGroupFieldsForParentException(exception: Exception, excep exception.mechanism = { ...exception.mechanism, - is_exception_group: true, + ...(exception.type === 'AggregateError' && { is_exception_group: true }), exception_id: exceptionId, }; } diff --git a/packages/utils/test/aggregate-errors.test.ts b/packages/utils/test/aggregate-errors.test.ts index 66b0f3fcfdb1..8d5fb3be6ded 100644 --- a/packages/utils/test/aggregate-errors.test.ts +++ b/packages/utils/test/aggregate-errors.test.ts @@ -4,8 +4,17 @@ import { applyAggregateErrorsToEvent, createStackParser } from '../src/index'; const stackParser = createStackParser([0, line => ({ filename: line })]); const exceptionFromError = (_stackParser: StackParser, ex: Error): Exception => { - return { value: ex.message, mechanism: { type: 'instrument', handled: true } }; + return { value: ex.message, type: ex.name, mechanism: { type: 'instrument', handled: true } }; }; +class FakeAggregateError extends Error { + public errors: Error[]; + + constructor(errors: Error[], message: string) { + super(message); + this.errors = errors; + this.name = 'AggregateError'; + } +} describe('applyAggregateErrorsToEvent()', () => { test('should not do anything if event does not contain an exception', () => { @@ -57,6 +66,7 @@ describe('applyAggregateErrorsToEvent()', () => { exception: { values: [ { + type: 'Error', value: 'Nested Error 2', mechanism: { exception_id: 2, @@ -67,22 +77,22 @@ describe('applyAggregateErrorsToEvent()', () => { }, }, { + type: 'Error', value: 'Nested Error 1', mechanism: { exception_id: 1, handled: true, parent_id: 0, - is_exception_group: true, source: 'cause', type: 'chained', }, }, { + type: 'Error', value: 'Root Error', mechanism: { exception_id: 0, handled: true, - is_exception_group: true, type: 'instrument', }, }, @@ -123,19 +133,21 @@ describe('applyAggregateErrorsToEvent()', () => { // Last exception in list should be the root exception expect(event.exception?.values?.[event.exception?.values.length - 1]).toStrictEqual({ + type: 'Error', value: 'Root Error', mechanism: { exception_id: 0, handled: true, - is_exception_group: true, type: 'instrument', }, }); }); test('should keep the original mechanism type for the root exception', () => { - const fakeAggregateError: ExtendedError = new Error('Root Error'); - fakeAggregateError.errors = [new Error('Nested Error 1'), new Error('Nested Error 2')]; + const fakeAggregateError = new FakeAggregateError( + [new Error('Nested Error 1'), new Error('Nested Error 2')], + 'Root Error', + ); const event: Event = { exception: { values: [exceptionFromError(stackParser, fakeAggregateError)] } }; const eventHint: EventHint = { originalException: fakeAggregateError }; @@ -147,10 +159,12 @@ describe('applyAggregateErrorsToEvent()', () => { test('should recursively walk mixed errors (Aggregate errors and based on `key`)', () => { const chainedError: ExtendedError = new Error('Nested Error 3'); chainedError.cause = new Error('Nested Error 4'); - const fakeAggregateError2: ExtendedError = new Error('AggregateError2'); - fakeAggregateError2.errors = [new Error('Nested Error 2'), chainedError]; - const fakeAggregateError1: ExtendedError = new Error('AggregateError1'); - fakeAggregateError1.errors = [new Error('Nested Error 1'), fakeAggregateError2]; + + const fakeAggregateError2 = new FakeAggregateError([new Error('Nested Error 2'), chainedError], 'AggregateError2'); + const fakeAggregateError1 = new FakeAggregateError( + [new Error('Nested Error 1'), fakeAggregateError2], + 'AggregateError1', + ); const event: Event = { exception: { values: [exceptionFromError(stackParser, fakeAggregateError1)] } }; const eventHint: EventHint = { originalException: fakeAggregateError1 }; @@ -167,17 +181,18 @@ describe('applyAggregateErrorsToEvent()', () => { source: 'cause', type: 'chained', }, + type: 'Error', value: 'Nested Error 4', }, { mechanism: { exception_id: 4, handled: true, - is_exception_group: true, parent_id: 2, source: 'errors[1]', type: 'chained', }, + type: 'Error', value: 'Nested Error 3', }, { @@ -188,6 +203,7 @@ describe('applyAggregateErrorsToEvent()', () => { source: 'errors[0]', type: 'chained', }, + type: 'Error', value: 'Nested Error 2', }, { @@ -199,6 +215,7 @@ describe('applyAggregateErrorsToEvent()', () => { source: 'errors[1]', type: 'chained', }, + type: 'AggregateError', value: 'AggregateError2', }, { @@ -209,6 +226,7 @@ describe('applyAggregateErrorsToEvent()', () => { source: 'errors[0]', type: 'chained', }, + type: 'Error', value: 'Nested Error 1', }, { @@ -218,6 +236,7 @@ describe('applyAggregateErrorsToEvent()', () => { is_exception_group: true, type: 'instrument', }, + type: 'AggregateError', value: 'AggregateError1', }, ], @@ -239,6 +258,7 @@ describe('applyAggregateErrorsToEvent()', () => { exception: { values: [ { + type: 'Error', value: 'Nested Error 2', mechanism: { exception_id: 2, @@ -249,22 +269,22 @@ describe('applyAggregateErrorsToEvent()', () => { }, }, { + type: 'Error', value: 'Nested Error 1', mechanism: { exception_id: 1, handled: true, parent_id: 0, - is_exception_group: true, source: 'cause', type: 'chained', }, }, { + type: 'Error', value: 'Root Error', mechanism: { exception_id: 0, handled: true, - is_exception_group: true, type: 'instrument', }, }, @@ -287,6 +307,7 @@ describe('applyAggregateErrorsToEvent()', () => { exception: { values: [ { + type: 'Error', value: 'Nested Error 2 ...', mechanism: { exception_id: 2, @@ -297,22 +318,22 @@ describe('applyAggregateErrorsToEvent()', () => { }, }, { + type: 'Error', value: 'Nested Error 1 ...', mechanism: { exception_id: 1, handled: true, parent_id: 0, - is_exception_group: true, source: 'cause', type: 'chained', }, }, { + type: 'Error', value: 'Root Error with...', mechanism: { exception_id: 0, handled: true, - is_exception_group: true, type: 'instrument', }, }, From 65d9150a5d13173abff2e38687b3391dd4be26fd Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Thu, 29 Feb 2024 15:44:28 +0000 Subject: [PATCH 07/17] feat(core): Remove deprecated props from `Span` interface (#10854) Instead, in places we need it we cast to a `SentrySpan` which still has the things in place, for now. With this, our span interface is _almost_ the same as for otel spans - missing are only: * Aligning `traceFlags` - this has apparently been updated in OTEL to have type `number` only, which makes this a bit easier. * Aligning `setStatus` which has a different signature in OTEL. I'll do these in follow ups! The biggest work here was fixing tests - I tried to rewrite tests to do less mocking where possible, which IMHO should cover actual functionality better than before (e.g. in svelte). --- packages/astro/test/server/meta.test.ts | 28 +- packages/core/src/scope.ts | 8 +- packages/core/src/tracing/errors.ts | 5 + packages/core/src/tracing/sentrySpan.ts | 6 +- packages/core/src/tracing/trace.ts | 12 +- packages/core/src/utils/getRootSpan.ts | 3 +- packages/core/src/utils/prepareEvent.ts | 3 +- packages/core/src/utils/spanUtils.ts | 11 +- packages/core/test/lib/hint.test.ts | 2 +- .../test/lib/integrations/requestdata.test.ts | 5 +- packages/core/test/lib/scope.test.ts | 14 +- packages/core/test/lib/sdk.test.ts | 2 +- packages/core/test/lib/tracing/errors.test.ts | 6 +- .../test/lib/tracing/idletransaction.test.ts | 13 +- packages/core/test/lib/tracing/trace.test.ts | 7 - packages/node/src/integrations/http.ts | 4 +- .../node/src/integrations/undici/index.ts | 5 +- .../opentelemetry-node/src/spanprocessor.ts | 7 +- .../opentelemetry-node/src/utils/spanMap.ts | 2 +- .../test/propagator.test.ts | 3 +- packages/opentelemetry/src/spanExporter.ts | 6 +- packages/react/test/profiler.test.tsx | 6 +- packages/svelte/src/performance.ts | 13 +- packages/svelte/test/performance.test.ts | 316 +++++++++++------- .../src/node/integrations/apollo.ts | 4 +- .../src/node/integrations/express.ts | 3 +- .../src/node/integrations/graphql.ts | 4 +- .../src/node/integrations/mongo.ts | 4 +- .../src/node/integrations/mysql.ts | 4 +- .../src/node/integrations/postgres.ts | 4 +- packages/types/src/span.ts | 120 +------ packages/types/src/transaction.ts | 25 +- packages/utils/src/requestdata.ts | 15 +- 33 files changed, 335 insertions(+), 335 deletions(-) diff --git a/packages/astro/test/server/meta.test.ts b/packages/astro/test/server/meta.test.ts index f235ad34d7ca..37506cb118b7 100644 --- a/packages/astro/test/server/meta.test.ts +++ b/packages/astro/test/server/meta.test.ts @@ -1,25 +1,23 @@ import * as SentryCore from '@sentry/core'; +import { SentrySpan } from '@sentry/core'; +import type { Transaction } from '@sentry/types'; import { vi } from 'vitest'; import { getTracingMetaTags, isValidBaggageString } from '../../src/server/meta'; const TRACE_FLAG_SAMPLED = 0x1; -const mockedSpan = { - isRecording: () => true, - spanContext: () => { - return { - traceId: '12345678901234567890123456789012', - spanId: '1234567890123456', - traceFlags: TRACE_FLAG_SAMPLED, - }; - }, - transaction: { - getDynamicSamplingContext: () => ({ - environment: 'production', - }), - }, -} as any; +const mockedSpan = new SentrySpan({ + traceId: '12345678901234567890123456789012', + spanId: '1234567890123456', + sampled: true, +}); +// eslint-disable-next-line deprecation/deprecation +mockedSpan.transaction = { + getDynamicSamplingContext: () => ({ + environment: 'production', + }), +} as Transaction; const mockedClient = {} as any; diff --git a/packages/core/src/scope.ts b/packages/core/src/scope.ts index 4646e5e5b015..820e41858135 100644 --- a/packages/core/src/scope.ts +++ b/packages/core/src/scope.ts @@ -26,6 +26,7 @@ import type { import { dateTimestampInSeconds, isPlainObject, logger, uuid4 } from '@sentry/utils'; import { updateSession } from './session'; +import type { SentrySpan } from './tracing/sentrySpan'; /** * Default value for maximum number of breadcrumbs added to an event. @@ -329,10 +330,15 @@ export class Scope implements ScopeInterface { // Often, this span (if it exists at all) will be a transaction, but it's not guaranteed to be. Regardless, it will // have a pointer to the currently-active transaction. const span = this._span; + // Cannot replace with getRootSpan because getRootSpan returns a span, not a transaction // Also, this method will be removed anyway. // eslint-disable-next-line deprecation/deprecation - return span && span.transaction; + if (span && (span as SentrySpan).transaction) { + // eslint-disable-next-line deprecation/deprecation + return (span as SentrySpan).transaction; + } + return undefined; } /** diff --git a/packages/core/src/tracing/errors.ts b/packages/core/src/tracing/errors.ts index 229695afc58c..f93486129c35 100644 --- a/packages/core/src/tracing/errors.ts +++ b/packages/core/src/tracing/errors.ts @@ -10,6 +10,11 @@ import { getActiveTransaction } from './utils'; let errorsInstrumented = false; +/** Only exposed for testing */ +export function _resetErrorsInstrumented(): void { + errorsInstrumented = false; +} + /** * Configures global error listeners */ diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index a478574b1c4e..9d1587d5fb60 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -1,6 +1,6 @@ import type { Primitive, - Span as SpanInterface, + Span, SpanAttributeValue, SpanAttributes, SpanContext, @@ -62,7 +62,7 @@ export class SpanRecorder { /** * Span contains all data about a span */ -export class SentrySpan implements SpanInterface { +export class SentrySpan implements Span { /** * Tags for the span. * @deprecated Use `spanToJSON(span).atttributes` instead. @@ -277,7 +277,7 @@ export class SentrySpan implements SpanInterface { */ public startChild( spanContext?: Pick>, - ): SpanInterface { + ): Span { const childSpan = new SentrySpan({ ...spanContext, parentSpanId: this._spanId, diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index a2030986616d..561f61ce9265 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -1,7 +1,6 @@ import type { Hub, Scope, Span, SpanTimeInput, StartSpanOptions, TransactionContext } from '@sentry/types'; import { dropUndefinedKeys, logger, tracingContextFromHeaders } from '@sentry/utils'; - import { getCurrentScope, getIsolationScope, withScope } from '../currentScopes'; import { DEBUG_BUILD } from '../debug-build'; @@ -10,6 +9,7 @@ import { handleCallbackErrors } from '../utils/handleCallbackErrors'; import { hasTracingEnabled } from '../utils/hasTracingEnabled'; import { spanIsSampled, spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils'; import { getDynamicSamplingContextFromSpan } from './dynamicSamplingContext'; +import type { SentrySpan } from './sentrySpan'; import { addChildSpanToSpan, getActiveSpan, setCapturedScopesOnSpan } from './utils'; /** @@ -30,7 +30,7 @@ export function startSpan(context: StartSpanOptions, callback: (span: Span | // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); // eslint-disable-next-line deprecation/deprecation - const parentSpan = scope.getSpan(); + const parentSpan = scope.getSpan() as SentrySpan | undefined; const shouldSkipSpan = context.onlyIfParent && !parentSpan; const activeSpan = shouldSkipSpan @@ -79,7 +79,7 @@ export function startSpanManual( // eslint-disable-next-line deprecation/deprecation const hub = getCurrentHub(); // eslint-disable-next-line deprecation/deprecation - const parentSpan = scope.getSpan(); + const parentSpan = scope.getSpan() as SentrySpan | undefined; const shouldSkipSpan = context.onlyIfParent && !parentSpan; const activeSpan = shouldSkipSpan @@ -130,8 +130,8 @@ export function startInactiveSpan(context: StartSpanOptions): Span | undefined { const hub = getCurrentHub(); const parentSpan = context.scope ? // eslint-disable-next-line deprecation/deprecation - context.scope.getSpan() - : getActiveSpan(); + (context.scope.getSpan() as SentrySpan | undefined) + : (getActiveSpan() as SentrySpan | undefined); const shouldSkipSpan = context.onlyIfParent && !parentSpan; @@ -264,7 +264,7 @@ function createChildSpanOrTransaction( forceTransaction, scope, }: { - parentSpan: Span | undefined; + parentSpan: SentrySpan | undefined; spanContext: TransactionContext; forceTransaction?: boolean; scope: Scope; diff --git a/packages/core/src/utils/getRootSpan.ts b/packages/core/src/utils/getRootSpan.ts index 9a0f5d642a77..fe6274c60670 100644 --- a/packages/core/src/utils/getRootSpan.ts +++ b/packages/core/src/utils/getRootSpan.ts @@ -1,4 +1,5 @@ import type { Span } from '@sentry/types'; +import type { SentrySpan } from './../tracing/sentrySpan'; /** * Returns the root span of a given span. @@ -11,5 +12,5 @@ import type { Span } from '@sentry/types'; export function getRootSpan(span: Span): Span | undefined { // TODO (v8): Remove this check and just return span // eslint-disable-next-line deprecation/deprecation - return span.transaction; + return (span as SentrySpan).transaction ? (span as SentrySpan).transaction : undefined; } diff --git a/packages/core/src/utils/prepareEvent.ts b/packages/core/src/utils/prepareEvent.ts index 8b0cc90df2fb..006ef75a5f13 100644 --- a/packages/core/src/utils/prepareEvent.ts +++ b/packages/core/src/utils/prepareEvent.ts @@ -332,8 +332,7 @@ function normalizeEvent(event: Event | null, depth: number, maxBreadth: number): if (data) { // This is a bit weird, as we generally have `Span` instances here, but to be safe we do not assume so - // eslint-disable-next-line deprecation/deprecation - span.data = normalize(data, depth, maxBreadth); + span.setAttributes(normalize(data, depth, maxBreadth)); } return span; diff --git a/packages/core/src/utils/spanUtils.ts b/packages/core/src/utils/spanUtils.ts index a23559576b9e..436d383b8a7c 100644 --- a/packages/core/src/utils/spanUtils.ts +++ b/packages/core/src/utils/spanUtils.ts @@ -62,6 +62,8 @@ function ensureTimestampInSeconds(timestamp: number): number { return isMs ? timestamp / 1000 : timestamp; } +type SpanWithToJSON = Span & { toJSON: () => SpanJSON }; + /** * Convert a span to a JSON representation. * Note that all fields returned here are optional and need to be guarded against. @@ -69,7 +71,6 @@ function ensureTimestampInSeconds(timestamp: number): number { * Note: Because of this, we currently have a circular type dependency (which we opted out of in package.json). * This is not avoidable as we need `spanToJSON` in `spanUtils.ts`, which in turn is needed by `span.ts` for backwards compatibility. * And `spanToJSON` needs the Span class from `span.ts` to check here. - * TODO v8: When we remove the deprecated stuff from `span.ts`, we can remove the circular dependency again. */ export function spanToJSON(span: Span): Partial { if (spanIsSentrySpan(span)) { @@ -77,12 +78,12 @@ export function spanToJSON(span: Span): Partial { } // Fallback: We also check for `.toJSON()` here... - // eslint-disable-next-line deprecation/deprecation - if (typeof span.toJSON === 'function') { - // eslint-disable-next-line deprecation/deprecation - return span.toJSON(); + if (typeof (span as SpanWithToJSON).toJSON === 'function') { + return (span as SpanWithToJSON).toJSON(); } + // TODO: Also handle OTEL spans here! + return {}; } diff --git a/packages/core/test/lib/hint.test.ts b/packages/core/test/lib/hint.test.ts index 5fb69ce39fff..25671b45262f 100644 --- a/packages/core/test/lib/hint.test.ts +++ b/packages/core/test/lib/hint.test.ts @@ -1,6 +1,6 @@ -import { captureEvent, getCurrentScope } from '@sentry/core'; import { GLOBAL_OBJ } from '@sentry/utils'; +import { captureEvent, getCurrentScope } from '../../src'; import { initAndBind } from '../../src/sdk'; import { TestClient, getDefaultTestClientOptions } from '../mocks/client'; import { AddAttachmentTestIntegration } from '../mocks/integration'; diff --git a/packages/core/test/lib/integrations/requestdata.test.ts b/packages/core/test/lib/integrations/requestdata.test.ts index e82638fd2e2e..723e9fa18260 100644 --- a/packages/core/test/lib/integrations/requestdata.test.ts +++ b/packages/core/test/lib/integrations/requestdata.test.ts @@ -1,9 +1,8 @@ import type { IncomingMessage } from 'http'; -import type { RequestDataIntegrationOptions } from '@sentry/core'; -import { setCurrentClient } from '@sentry/core'; -import { RequestData } from '@sentry/core'; import type { Event, EventProcessor } from '@sentry/types'; import * as sentryUtils from '@sentry/utils'; +import type { RequestDataIntegrationOptions } from '../../../src'; +import { RequestData, setCurrentClient } from '../../../src'; import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; diff --git a/packages/core/test/lib/scope.test.ts b/packages/core/test/lib/scope.test.ts index 30697275db2f..48a39d7f09b0 100644 --- a/packages/core/test/lib/scope.test.ts +++ b/packages/core/test/lib/scope.test.ts @@ -961,18 +961,16 @@ describe('withActiveSpan()', () => { }); }); - it('should create child spans when calling startSpan within the callback', done => { - expect.assertions(2); + it('should create child spans when calling startSpan within the callback', () => { const inactiveSpan = startInactiveSpan({ name: 'inactive-span' }); - withActiveSpan(inactiveSpan!, () => { - startSpan({ name: 'child-span' }, childSpan => { - // eslint-disable-next-line deprecation/deprecation - expect(childSpan?.parentSpanId).toBe(inactiveSpan?.spanContext().spanId); - expect(spanToJSON(childSpan!).parent_span_id).toBe(inactiveSpan?.spanContext().spanId); - done(); + const parentSpanId = withActiveSpan(inactiveSpan!, () => { + return startSpan({ name: 'child-span' }, childSpan => { + return spanToJSON(childSpan!).parent_span_id; }); }); + + expect(parentSpanId).toBe(inactiveSpan?.spanContext().spanId); }); it('when `null` is passed, no span should be active within the callback', () => { diff --git a/packages/core/test/lib/sdk.test.ts b/packages/core/test/lib/sdk.test.ts index ad3551b783da..0117585d05ab 100644 --- a/packages/core/test/lib/sdk.test.ts +++ b/packages/core/test/lib/sdk.test.ts @@ -1,5 +1,5 @@ -import { captureCheckIn, getCurrentScope, setCurrentClient } from '@sentry/core'; import type { Client, Integration, IntegrationFnResult } from '@sentry/types'; +import { captureCheckIn, getCurrentScope, setCurrentClient } from '../../src'; import { installedIntegrations } from '../../src/integration'; import { initAndBind } from '../../src/sdk'; diff --git a/packages/core/test/lib/tracing/errors.test.ts b/packages/core/test/lib/tracing/errors.test.ts index 29377d3fed09..24c19a24121d 100644 --- a/packages/core/test/lib/tracing/errors.test.ts +++ b/packages/core/test/lib/tracing/errors.test.ts @@ -1,13 +1,14 @@ -import { addTracingExtensions, setCurrentClient, spanToJSON, startInactiveSpan, startSpan } from '@sentry/core'; import type { HandlerDataError, HandlerDataUnhandledRejection } from '@sentry/types'; +import { addTracingExtensions, setCurrentClient, spanToJSON, startInactiveSpan, startSpan } from '../../../src'; -import { registerErrorInstrumentation } from '../../../src/tracing/errors'; +import { _resetErrorsInstrumented, registerErrorInstrumentation } from '../../../src/tracing/errors'; import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; const mockAddGlobalErrorInstrumentationHandler = jest.fn(); const mockAddGlobalUnhandledRejectionInstrumentationHandler = jest.fn(); let mockErrorCallback: (data: HandlerDataError) => void = () => {}; let mockUnhandledRejectionCallback: (data: HandlerDataUnhandledRejection) => void = () => {}; + jest.mock('@sentry/utils', () => { const actual = jest.requireActual('@sentry/utils'); return { @@ -36,6 +37,7 @@ describe('registerErrorHandlers()', () => { const client = new TestClient(options); setCurrentClient(client); client.init(); + _resetErrorsInstrumented(); }); it('registers error instrumentation', () => { diff --git a/packages/core/test/lib/tracing/idletransaction.test.ts b/packages/core/test/lib/tracing/idletransaction.test.ts index fae249227bb0..56ed93abe5d9 100644 --- a/packages/core/test/lib/tracing/idletransaction.test.ts +++ b/packages/core/test/lib/tracing/idletransaction.test.ts @@ -1,6 +1,12 @@ +/* eslint-disable deprecation/deprecation */ +import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; + import { + IdleTransaction, + SentrySpan, TRACING_DEFAULTS, Transaction, + getClient, getCurrentHub, getCurrentScope, getGlobalScope, @@ -10,11 +16,7 @@ import { startInactiveSpan, startSpan, startSpanManual, -} from '@sentry/core'; -/* eslint-disable deprecation/deprecation */ -import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; - -import { IdleTransaction, SentrySpan, getClient } from '../../../src'; +} from '../../../src'; import { IdleTransactionSpanRecorder } from '../../../src/tracing/idletransaction'; const dsn = 'https://123@sentry.io/42'; @@ -47,6 +49,7 @@ describe('IdleTransaction', () => { transaction.initSpanRecorder(10); const scope = getCurrentScope(); + // eslint-disable-next-line deprecation/deprecation expect(scope.getTransaction()).toBe(transaction); }); diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 56f15b6dcce4..bd298fd4d5ff 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -287,10 +287,7 @@ describe('startSpan', () => { expect(getCurrentScope()).not.toBe(initialScope); expect(getCurrentScope()).toBe(manualScope); expect(getActiveSpan()).toBe(span); - expect(spanToJSON(span!).parent_span_id).toBe('parent-span-id'); - // eslint-disable-next-line deprecation/deprecation - expect(span?.parentSpanId).toBe('parent-span-id'); }); expect(getCurrentScope()).toBe(initialScope); @@ -565,8 +562,6 @@ describe('startSpanManual', () => { expect(getCurrentScope()).toBe(manualScope); expect(getActiveSpan()).toBe(span); expect(spanToJSON(span!).parent_span_id).toBe('parent-span-id'); - // eslint-disable-next-line deprecation/deprecation - expect(span?.parentSpanId).toBe('parent-span-id'); finish(); @@ -789,8 +784,6 @@ describe('startInactiveSpan', () => { expect(span).toBeDefined(); expect(spanToJSON(span!).parent_span_id).toBe('parent-span-id'); - // eslint-disable-next-line deprecation/deprecation - expect(span?.parentSpanId).toBe('parent-span-id'); expect(getActiveSpan()).toBeUndefined(); span?.end(); diff --git a/packages/node/src/integrations/http.ts b/packages/node/src/integrations/http.ts index 22407ca77e91..00858d6b15cf 100644 --- a/packages/node/src/integrations/http.ts +++ b/packages/node/src/integrations/http.ts @@ -1,7 +1,7 @@ /* eslint-disable max-lines */ import type * as http from 'http'; import type * as https from 'https'; -import type { Hub } from '@sentry/core'; +import type { Hub, SentrySpan } from '@sentry/core'; import { defineIntegration, getIsolationScope, hasTracingEnabled } from '@sentry/core'; import { addBreadcrumb, @@ -319,7 +319,7 @@ function _createWrappedRequestMethodFactory( const scope = getCurrentScope(); const isolationScope = getIsolationScope(); - const parentSpan = getActiveSpan(); + const parentSpan = getActiveSpan() as SentrySpan; const data = getRequestSpanData(requestUrl, requestOptions); diff --git a/packages/node/src/integrations/undici/index.ts b/packages/node/src/integrations/undici/index.ts index b76caf647fae..222c75f852d8 100644 --- a/packages/node/src/integrations/undici/index.ts +++ b/packages/node/src/integrations/undici/index.ts @@ -1,3 +1,4 @@ +import type { SentrySpan } from '@sentry/core'; import { addBreadcrumb, defineIntegration, @@ -183,7 +184,7 @@ export class Undici implements Integration { const clientOptions = client.getOptions(); const scope = getCurrentScope(); const isolationScope = getIsolationScope(); - const parentSpan = getActiveSpan(); + const parentSpan = getActiveSpan() as SentrySpan; const span = this._shouldCreateSpan(stringUrl) ? createRequestSpan(parentSpan, request, stringUrl) : undefined; if (span) { @@ -320,7 +321,7 @@ function setHeadersOnRequest( } function createRequestSpan( - activeSpan: Span | undefined, + activeSpan: SentrySpan | undefined, request: RequestWithSentry, stringUrl: string, ): Span | undefined { diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index 4ef437a6e88e..7788858c586d 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -2,6 +2,7 @@ import type { Context } from '@opentelemetry/api'; import { SpanKind, context, trace } from '@opentelemetry/api'; import { suppressTracing } from '@opentelemetry/core'; import type { Span as OtelSpan, SpanProcessor as OtelSpanProcessor } from '@opentelemetry/sdk-trace-base'; +import type { SentrySpan } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, @@ -11,7 +12,7 @@ import { getClient, getCurrentHub, } from '@sentry/core'; -import type { DynamicSamplingContext, Span as SentrySpan, TraceparentData, TransactionContext } from '@sentry/types'; +import type { DynamicSamplingContext, TraceparentData, TransactionContext } from '@sentry/types'; import { logger } from '@sentry/utils'; import { SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, SENTRY_TRACE_PARENT_CONTEXT_KEY } from './constants'; @@ -69,7 +70,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor { name: otelSpan.name, startTimestamp: convertOtelTimeToSeconds(otelSpan.startTime), spanId: otelSpanId, - }); + }) as SentrySpan; setSentrySpan(otelSpanId, sentryChildSpan); } else { @@ -83,7 +84,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor { spanId: otelSpanId, }); - setSentrySpan(otelSpanId, transaction); + setSentrySpan(otelSpanId, transaction as unknown as SentrySpan); } } diff --git a/packages/opentelemetry-node/src/utils/spanMap.ts b/packages/opentelemetry-node/src/utils/spanMap.ts index 9cbdba4460ab..49e4c033403e 100644 --- a/packages/opentelemetry-node/src/utils/spanMap.ts +++ b/packages/opentelemetry-node/src/utils/spanMap.ts @@ -1,5 +1,5 @@ +import type { SentrySpan } from '@sentry/core'; import { getRootSpan } from '@sentry/core'; -import type { Span as SentrySpan } from '@sentry/types'; interface SpanMapEntry { sentrySpan: SentrySpan; diff --git a/packages/opentelemetry-node/test/propagator.test.ts b/packages/opentelemetry-node/test/propagator.test.ts index 6067b5d7e90d..550ec2633843 100644 --- a/packages/opentelemetry-node/test/propagator.test.ts +++ b/packages/opentelemetry-node/test/propagator.test.ts @@ -7,6 +7,7 @@ import { trace, } from '@opentelemetry/api'; import { suppressTracing } from '@opentelemetry/core'; +import type { SentrySpan } from '@sentry/core'; import { Transaction, addTracingExtensions, getCurrentHub, setCurrentClient } from '@sentry/core'; import type { Client, TransactionContext } from '@sentry/types'; @@ -67,7 +68,7 @@ describe('SentryPropagator', () => { // eslint-disable-next-line @typescript-eslint/no-unused-vars const { spanId, ...ctx } = transactionContext; // eslint-disable-next-line deprecation/deprecation - const span = transaction.startChild({ ...ctx, name: transactionContext.name }); + const span = transaction.startChild({ ...ctx, name: transactionContext.name }) as SentrySpan; setSentrySpan(span.spanContext().spanId, span); } } diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index 14f5fdc9fef8..b673930be9a4 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -3,7 +3,7 @@ import type { ExportResult } from '@opentelemetry/core'; import { ExportResultCode } from '@opentelemetry/core'; import type { ReadableSpan, SpanExporter } from '@opentelemetry/sdk-trace-base'; import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import type { Transaction } from '@sentry/core'; +import type { SentrySpan, Transaction } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, @@ -11,7 +11,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getCurrentHub, } from '@sentry/core'; -import type { Scope, Span as SentrySpan, SpanOrigin, TransactionSource } from '@sentry/types'; +import type { Scope, SpanOrigin, TransactionSource } from '@sentry/types'; import { addNonEnumerableProperty, dropUndefinedKeys, logger } from '@sentry/utils'; import { startTransaction } from './custom/transaction'; @@ -241,7 +241,7 @@ function createAndFinishSpanForOtelSpan(node: SpanNode, sentryParentSpan: Sentry startTimestamp: convertOtelTimeToSeconds(span.startTime), spanId, origin, - }); + }) as SentrySpan; sentrySpan.setStatus(mapStatus(span)); node.children.forEach(child => { diff --git a/packages/react/test/profiler.test.tsx b/packages/react/test/profiler.test.tsx index 5d399f342535..de770b362473 100644 --- a/packages/react/test/profiler.test.tsx +++ b/packages/react/test/profiler.test.tsx @@ -1,3 +1,4 @@ +import { SentrySpan } from '@sentry/core'; import type { SpanContext } from '@sentry/types'; import { render } from '@testing-library/react'; import { renderHook } from '@testing-library/react-hooks'; @@ -10,10 +11,7 @@ import { UNKNOWN_COMPONENT, useProfiler, withProfiler } from '../src/profiler'; const mockStartInactiveSpan = jest.fn((spanArgs: SpanContext) => ({ ...spanArgs })); const mockFinish = jest.fn(); -// @sent -class MockSpan { - public constructor(public readonly ctx: SpanContext) {} - +class MockSpan extends SentrySpan { public end(): void { mockFinish(); } diff --git a/packages/svelte/src/performance.ts b/packages/svelte/src/performance.ts index 8cc3e86017ed..302f346e32b9 100644 --- a/packages/svelte/src/performance.ts +++ b/packages/svelte/src/performance.ts @@ -3,7 +3,7 @@ import type { Span, Transaction } from '@sentry/types'; import { afterUpdate, beforeUpdate, onMount } from 'svelte'; import { current_component } from 'svelte/internal'; -import { getRootSpan } from '@sentry/core'; +import { getRootSpan, startInactiveSpan, withActiveSpan } from '@sentry/core'; import { DEFAULT_COMPONENT_NAME, UI_SVELTE_INIT, UI_SVELTE_UPDATE } from './constants'; import type { TrackComponentOptions } from './types'; @@ -77,11 +77,12 @@ function recordUpdateSpans(componentName: string, initSpan?: Span): void { const parentSpan = initSpan && initSpan.isRecording() && getRootSpan(initSpan) === transaction ? initSpan : transaction; - // eslint-disable-next-line deprecation/deprecation - updateSpan = parentSpan.startChild({ - op: UI_SVELTE_UPDATE, - name: componentName, - origin: 'auto.ui.svelte', + updateSpan = withActiveSpan(parentSpan, () => { + return startInactiveSpan({ + op: UI_SVELTE_UPDATE, + name: componentName, + origin: 'auto.ui.svelte', + }); }); }); diff --git a/packages/svelte/test/performance.test.ts b/packages/svelte/test/performance.test.ts index 1d90b0b9ab79..8e7357da044e 100644 --- a/packages/svelte/test/performance.test.ts +++ b/packages/svelte/test/performance.test.ts @@ -1,197 +1,259 @@ -import type { Scope } from '@sentry/core'; import { act, render } from '@testing-library/svelte'; - +import { + addTracingExtensions, + getClient, + getCurrentScope, + getIsolationScope, + init, + spanToJSON, + startSpan, +} from '../src'; + +import type { TransactionEvent } from '@sentry/types'; import { vi } from 'vitest'; // linter doesn't like Svelte component imports import DummyComponent from './components/Dummy.svelte'; -let returnUndefinedTransaction = false; - -const testTransaction: { spans: any[]; startChild: jest.Mock; end: jest.Mock; isRecording: () => boolean } = { - spans: [], - startChild: vi.fn(), - end: vi.fn(), - isRecording: () => true, -}; -const testUpdateSpan = { end: vi.fn() }; -const testInitSpan: any = { - transaction: testTransaction, - end: vi.fn(), - startChild: vi.fn(), - isRecording: () => true, -}; - -vi.mock('@sentry/core', async () => { - const original = await vi.importActual('@sentry/core'); - return { - ...original, - getCurrentScope(): Scope { - return { - getTransaction: () => { - return returnUndefinedTransaction ? undefined : testTransaction; - }, - } as Scope; - }, - }; -}); +const PUBLIC_DSN = 'https://username@domain/123'; describe('Sentry.trackComponent()', () => { + const transactions: TransactionEvent[] = []; + beforeEach(() => { + transactions.splice(0, transactions.length); + vi.resetAllMocks(); - testTransaction.spans = []; - testTransaction.startChild.mockImplementation(spanCtx => { - testTransaction.spans.push(spanCtx); - return testInitSpan; - }); + getCurrentScope().clear(); + getIsolationScope().clear(); + + addTracingExtensions(); - testInitSpan.startChild.mockImplementation((spanCtx: any) => { - testTransaction.spans.push(spanCtx); - return testUpdateSpan; + const beforeSendTransaction = vi.fn(event => { + transactions.push(event); + return null; }); - testInitSpan.end = vi.fn(); - testInitSpan.isRecording = () => true; - returnUndefinedTransaction = false; + init({ + dsn: PUBLIC_DSN, + enableTracing: true, + beforeSendTransaction, + }); }); - it('creates nested init and update spans on component initialization', () => { - render(DummyComponent, { props: { options: {} } }); + it('creates nested init and update spans on component initialization', async () => { + startSpan({ name: 'outer' }, span => { + expect(span).toBeDefined(); + render(DummyComponent, { props: { options: {} } }); + }); + + await getClient()?.flush(); + + expect(transactions).toHaveLength(1); + const transaction = transactions[0]; + expect(transaction.spans).toHaveLength(2); + + const rootSpanId = transaction.contexts?.trace?.span_id; + expect(rootSpanId).toBeDefined(); - expect(testTransaction.startChild).toHaveBeenCalledWith({ - name: '', + const initSpanId = transaction.spans![0].spanContext().spanId; + + expect(spanToJSON(transaction.spans![0])).toEqual({ + data: { + 'sentry.op': 'ui.svelte.init', + 'sentry.origin': 'auto.ui.svelte', + }, + description: '', op: 'ui.svelte.init', origin: 'auto.ui.svelte', + parent_span_id: rootSpanId, + span_id: initSpanId, + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), }); - expect(testInitSpan.startChild).toHaveBeenCalledWith({ - name: '', + expect(spanToJSON(transaction.spans![1])).toEqual({ + data: { + 'sentry.op': 'ui.svelte.update', + 'sentry.origin': 'auto.ui.svelte', + }, + description: '', op: 'ui.svelte.update', origin: 'auto.ui.svelte', + parent_span_id: initSpanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), }); - - expect(testInitSpan.end).toHaveBeenCalledTimes(1); - expect(testUpdateSpan.end).toHaveBeenCalledTimes(1); - expect(testTransaction.spans.length).toEqual(2); }); it('creates an update span, when the component is updated', async () => { - // Make the end() function actually end the initSpan - testInitSpan.end.mockImplementation(() => { - testInitSpan.isRecording = () => false; + startSpan({ name: 'outer' }, async span => { + expect(span).toBeDefined(); + + // first we create the component + const { component } = render(DummyComponent, { props: { options: {} } }); + + // then trigger an update + // (just changing the trackUpdates prop so that we trigger an update. # + // The value doesn't do anything here) + await act(() => component.$set({ options: { trackUpdates: true } })); }); - // first we create the component - const { component } = render(DummyComponent, { props: { options: {} } }); + await getClient()?.flush(); - // then trigger an update - // (just changing the trackUpdates prop so that we trigger an update. # - // The value doesn't do anything here) - await act(() => component.$set({ options: { trackUpdates: true } })); + expect(transactions).toHaveLength(1); + const transaction = transactions[0]; + expect(transaction.spans).toHaveLength(3); + + const rootSpanId = transaction.contexts?.trace?.span_id; + expect(rootSpanId).toBeDefined(); + + const initSpanId = transaction.spans![0].spanContext().spanId; - // once for init (unimportant here), once for starting the update span - expect(testTransaction.startChild).toHaveBeenCalledTimes(2); - expect(testTransaction.startChild).toHaveBeenLastCalledWith({ - name: '', + expect(spanToJSON(transaction.spans![0])).toEqual({ + data: { + 'sentry.op': 'ui.svelte.init', + 'sentry.origin': 'auto.ui.svelte', + }, + description: '', + op: 'ui.svelte.init', + origin: 'auto.ui.svelte', + parent_span_id: rootSpanId, + span_id: initSpanId, + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + }); + + expect(spanToJSON(transaction.spans![1])).toEqual({ + data: { + 'sentry.op': 'ui.svelte.update', + 'sentry.origin': 'auto.ui.svelte', + }, + description: '', + op: 'ui.svelte.update', + origin: 'auto.ui.svelte', + parent_span_id: initSpanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), + }); + + expect(spanToJSON(transaction.spans![2])).toEqual({ + data: { + 'sentry.op': 'ui.svelte.update', + 'sentry.origin': 'auto.ui.svelte', + }, + description: '', op: 'ui.svelte.update', origin: 'auto.ui.svelte', + parent_span_id: rootSpanId, + span_id: expect.any(String), + start_timestamp: expect.any(Number), + timestamp: expect.any(Number), + trace_id: expect.any(String), }); - expect(testTransaction.spans.length).toEqual(3); }); - it('only creates init spans if trackUpdates is deactivated', () => { - render(DummyComponent, { props: { options: { trackUpdates: false } } }); + it('only creates init spans if trackUpdates is deactivated', async () => { + startSpan({ name: 'outer' }, async span => { + expect(span).toBeDefined(); - expect(testTransaction.startChild).toHaveBeenCalledWith({ - name: '', - op: 'ui.svelte.init', - origin: 'auto.ui.svelte', + render(DummyComponent, { props: { options: { trackUpdates: false } } }); }); - expect(testInitSpan.startChild).not.toHaveBeenCalled(); + await getClient()?.flush(); + + expect(transactions).toHaveLength(1); + const transaction = transactions[0]; + expect(transaction.spans).toHaveLength(1); - expect(testInitSpan.end).toHaveBeenCalledTimes(1); - expect(testTransaction.spans.length).toEqual(1); + expect(spanToJSON(transaction.spans![0]).op).toEqual('ui.svelte.init'); }); - it('only creates update spans if trackInit is deactivated', () => { - render(DummyComponent, { props: { options: { trackInit: false } } }); + it('only creates update spans if trackInit is deactivated', async () => { + startSpan({ name: 'outer' }, span => { + expect(span).toBeDefined(); - expect(testTransaction.startChild).toHaveBeenCalledWith({ - name: '', - op: 'ui.svelte.update', - origin: 'auto.ui.svelte', + render(DummyComponent, { props: { options: { trackInit: false } } }); }); - expect(testInitSpan.startChild).not.toHaveBeenCalled(); + await getClient()?.flush(); - expect(testInitSpan.end).toHaveBeenCalledTimes(1); - expect(testTransaction.spans.length).toEqual(1); + expect(transactions).toHaveLength(1); + const transaction = transactions[0]; + expect(transaction.spans).toHaveLength(1); + + expect(spanToJSON(transaction.spans![0]).op).toEqual('ui.svelte.update'); }); - it('creates no spans if trackInit and trackUpdates are deactivated', () => { - render(DummyComponent, { props: { options: { trackInit: false, trackUpdates: false } } }); + it('creates no spans if trackInit and trackUpdates are deactivated', async () => { + startSpan({ name: 'outer' }, span => { + expect(span).toBeDefined(); + + render(DummyComponent, { props: { options: { trackInit: false, trackUpdates: false } } }); + }); + + await getClient()?.flush(); - expect(testTransaction.startChild).not.toHaveBeenCalled(); - expect(testInitSpan.startChild).not.toHaveBeenCalled(); - expect(testTransaction.spans.length).toEqual(0); + expect(transactions).toHaveLength(1); + const transaction = transactions[0]; + expect(transaction.spans).toHaveLength(0); }); it('sets a custom component name as a span name if `componentName` is provided', async () => { - render(DummyComponent, { - props: { options: { componentName: 'CustomComponentName' } }, - }); + startSpan({ name: 'outer' }, span => { + expect(span).toBeDefined(); - expect(testTransaction.startChild).toHaveBeenCalledWith({ - name: '', - op: 'ui.svelte.init', - origin: 'auto.ui.svelte', + render(DummyComponent, { + props: { options: { componentName: 'CustomComponentName' } }, + }); }); - expect(testInitSpan.startChild).toHaveBeenCalledWith({ - name: '', - op: 'ui.svelte.update', - origin: 'auto.ui.svelte', - }); + await getClient()?.flush(); - expect(testInitSpan.end).toHaveBeenCalledTimes(1); - expect(testUpdateSpan.end).toHaveBeenCalledTimes(1); - expect(testTransaction.spans.length).toEqual(2); + expect(transactions).toHaveLength(1); + const transaction = transactions[0]; + expect(transaction.spans).toHaveLength(2); + + expect(spanToJSON(transaction.spans![0]).description).toEqual(''); + expect(spanToJSON(transaction.spans![1]).description).toEqual(''); }); it("doesn't do anything, if there's no ongoing transaction", async () => { - returnUndefinedTransaction = true; - render(DummyComponent, { props: { options: { componentName: 'CustomComponentName' } }, }); - expect(testInitSpan.end).toHaveBeenCalledTimes(0); - expect(testUpdateSpan.end).toHaveBeenCalledTimes(0); - expect(testTransaction.spans.length).toEqual(0); + await getClient()?.flush(); + + expect(transactions).toHaveLength(0); }); - it("doesn't record update spans, if there's no ongoing transaction at that time", async () => { - // Make the end() function actually end the initSpan - testInitSpan.end.mockImplementation(() => { - testInitSpan.isRecording = () => false; - }); + it("doesn't record update spans, if there's no ongoing root span at that time", async () => { + const component = startSpan({ name: 'outer' }, span => { + expect(span).toBeDefined(); - // first we create the component - const { component } = render(DummyComponent, { props: { options: {} } }); + const { component } = render(DummyComponent, { props: { options: {} } }); + return component; + }); - // then clear the current transaction and trigger an update - returnUndefinedTransaction = true; + // then trigger an update after the root span ended - should not record update span await act(() => component.$set({ options: { trackUpdates: true } })); - // we should only record the init spans (including the initial update) - // but not the second update - expect(testTransaction.startChild).toHaveBeenCalledTimes(1); - expect(testTransaction.startChild).toHaveBeenLastCalledWith({ - name: '', - op: 'ui.svelte.init', - origin: 'auto.ui.svelte', - }); - expect(testTransaction.spans.length).toEqual(2); + await getClient()?.flush(); + + expect(transactions).toHaveLength(1); + const transaction = transactions[0]; + + // One update span is triggered by the initial rendering, but the second one is not captured + expect(transaction.spans).toHaveLength(2); + + expect(spanToJSON(transaction.spans![0]).op).toEqual('ui.svelte.init'); + expect(spanToJSON(transaction.spans![1]).op).toEqual('ui.svelte.update'); }); }); diff --git a/packages/tracing-internal/src/node/integrations/apollo.ts b/packages/tracing-internal/src/node/integrations/apollo.ts index d2a7f0ff73ee..2b4268239d63 100644 --- a/packages/tracing-internal/src/node/integrations/apollo.ts +++ b/packages/tracing-internal/src/node/integrations/apollo.ts @@ -1,4 +1,4 @@ -import type { Hub } from '@sentry/core'; +import type { Hub, SentrySpan } from '@sentry/core'; import type { EventProcessor } from '@sentry/types'; import { arrayify, fill, isThenable, loadModule, logger } from '@sentry/utils'; @@ -185,7 +185,7 @@ function wrapResolver( // eslint-disable-next-line deprecation/deprecation const scope = getCurrentHub().getScope(); // eslint-disable-next-line deprecation/deprecation - const parentSpan = scope.getSpan(); + const parentSpan = scope.getSpan() as SentrySpan | undefined; // eslint-disable-next-line deprecation/deprecation const span = parentSpan?.startChild({ name: `${resolverGroupName}.${resolverName}`, diff --git a/packages/tracing-internal/src/node/integrations/express.ts b/packages/tracing-internal/src/node/integrations/express.ts index e1417c0f1773..b6012bad9a72 100644 --- a/packages/tracing-internal/src/node/integrations/express.ts +++ b/packages/tracing-internal/src/node/integrations/express.ts @@ -1,6 +1,7 @@ /* eslint-disable max-lines */ +import type { Transaction } from '@sentry/core'; import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, spanToJSON } from '@sentry/core'; -import type { Integration, PolymorphicRequest, Transaction } from '@sentry/types'; +import type { Integration, PolymorphicRequest } from '@sentry/types'; import { GLOBAL_OBJ, extractPathForTransaction, diff --git a/packages/tracing-internal/src/node/integrations/graphql.ts b/packages/tracing-internal/src/node/integrations/graphql.ts index 16256e0dccab..b2ddee7530f3 100644 --- a/packages/tracing-internal/src/node/integrations/graphql.ts +++ b/packages/tracing-internal/src/node/integrations/graphql.ts @@ -1,4 +1,4 @@ -import type { Hub } from '@sentry/core'; +import type { Hub, SentrySpan } from '@sentry/core'; import type { EventProcessor } from '@sentry/types'; import { fill, isThenable, loadModule, logger } from '@sentry/utils'; @@ -48,7 +48,7 @@ export class GraphQL implements LazyLoadedIntegration { // eslint-disable-next-line deprecation/deprecation const scope = getCurrentHub().getScope(); // eslint-disable-next-line deprecation/deprecation - const parentSpan = scope.getSpan(); + const parentSpan = scope.getSpan() as SentrySpan | undefined; // eslint-disable-next-line deprecation/deprecation const span = parentSpan?.startChild({ diff --git a/packages/tracing-internal/src/node/integrations/mongo.ts b/packages/tracing-internal/src/node/integrations/mongo.ts index 8496ed422821..e052eafa6378 100644 --- a/packages/tracing-internal/src/node/integrations/mongo.ts +++ b/packages/tracing-internal/src/node/integrations/mongo.ts @@ -1,4 +1,4 @@ -import type { Hub } from '@sentry/core'; +import type { Hub, SentrySpan } from '@sentry/core'; import type { EventProcessor, SpanContext } from '@sentry/types'; import { fill, isThenable, loadModule, logger } from '@sentry/utils'; @@ -174,7 +174,7 @@ export class Mongo implements LazyLoadedIntegration { // eslint-disable-next-line deprecation/deprecation const client = hub.getClient(); // eslint-disable-next-line deprecation/deprecation - const parentSpan = scope.getSpan(); + const parentSpan = scope.getSpan() as SentrySpan | undefined; const sendDefaultPii = client?.getOptions().sendDefaultPii; diff --git a/packages/tracing-internal/src/node/integrations/mysql.ts b/packages/tracing-internal/src/node/integrations/mysql.ts index d7349f804aae..3cca1c8d5ccd 100644 --- a/packages/tracing-internal/src/node/integrations/mysql.ts +++ b/packages/tracing-internal/src/node/integrations/mysql.ts @@ -1,4 +1,4 @@ -import type { Hub } from '@sentry/core'; +import type { Hub, SentrySpan } from '@sentry/core'; import type { EventProcessor, Span } from '@sentry/types'; import { fill, loadModule, logger } from '@sentry/utils'; @@ -100,7 +100,7 @@ export class Mysql implements LazyLoadedIntegration { // eslint-disable-next-line deprecation/deprecation const scope = getCurrentHub().getScope(); // eslint-disable-next-line deprecation/deprecation - const parentSpan = scope.getSpan(); + const parentSpan = scope.getSpan() as SentrySpan | undefined; // eslint-disable-next-line deprecation/deprecation const span = parentSpan?.startChild({ diff --git a/packages/tracing-internal/src/node/integrations/postgres.ts b/packages/tracing-internal/src/node/integrations/postgres.ts index 9b6cb9fd77dc..3c883bb64de1 100644 --- a/packages/tracing-internal/src/node/integrations/postgres.ts +++ b/packages/tracing-internal/src/node/integrations/postgres.ts @@ -1,4 +1,4 @@ -import type { Hub } from '@sentry/core'; +import type { Hub, SentrySpan } from '@sentry/core'; import type { EventProcessor } from '@sentry/types'; import { fill, isThenable, loadModule, logger } from '@sentry/utils'; @@ -101,7 +101,7 @@ export class Postgres implements LazyLoadedIntegration { // eslint-disable-next-line deprecation/deprecation const scope = getCurrentHub().getScope(); // eslint-disable-next-line deprecation/deprecation - const parentSpan = scope.getSpan(); + const parentSpan = scope.getSpan() as SentrySpan | undefined; const data: Record = { 'db.system': 'postgresql', diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index 1818b7e99995..018e84e977ec 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -1,7 +1,6 @@ -import type { TraceContext } from './context'; import type { Primitive } from './misc'; import type { HrTime } from './opentelemetry'; -import type { Transaction, TransactionSource } from './transaction'; +import type { TransactionSource } from './transaction'; type SpanOriginType = 'manual' | 'auto'; type SpanOriginCategory = string; // e.g. http, db, ui, .... @@ -97,7 +96,10 @@ export interface SpanContextData { // Note: we do not have traceState here, but this is optional in OpenTelemetry anyhow } -/** Interface holding all properties that can be set on a Span on creation. */ +/** + * Interface holding all properties that can be set on a Span on creation. + * This is only used for the legacy span/transaction creation and will go away in v8. + */ export interface SpanContext { /** * Human-readable identifier for the span. @@ -162,69 +164,10 @@ export interface SpanContext { origin?: SpanOrigin | undefined; } -/** Span holding trace_id, span_id */ -export interface Span extends Omit { - /** - * The ID of the span. - * @deprecated Use `spanContext().spanId` instead. - */ - spanId: string; - - /** - * Parent Span ID - * - * @deprecated Use `spanToJSON(span).parent_span_id` instead. - */ - parentSpanId?: string | undefined; - - /** - * The ID of the trace. - * @deprecated Use `spanContext().traceId` instead. - */ - traceId: string; - - /** - * Was this span chosen to be sent as part of the sample? - * @deprecated Use `isRecording()` instead. - */ - sampled?: boolean | undefined; - - /** - * Timestamp in seconds (epoch time) indicating when the span started. - * @deprecated Use `spanToJSON()` instead. - */ - startTimestamp: number; - - /** - * Timestamp in seconds (epoch time) indicating when the span ended. - * @deprecated Use `spanToJSON()` instead. - */ - endTimestamp?: number | undefined; - - /** - * Tags for the span. - * @deprecated Use `spanToJSON(span).atttributes` instead. - */ - tags: { [key: string]: Primitive }; - - /** - * Data for the span. - * @deprecated Use `spanToJSON(span).atttributes` instead. - */ - data: { [key: string]: any }; - - /** - * Attributes for the span. - * @deprecated Use `spanToJSON(span).atttributes` instead. - */ - attributes: SpanAttributes; - - /** - * The transaction containing this span - * @deprecated Use top level `Sentry.getRootSpan()` instead - */ - transaction?: Transaction; - +/** + * A generic Span which holds trace data. + */ +export interface Span { /** * Get context data for this span. * This includes the spanId & the traceId. @@ -236,25 +179,6 @@ export interface Span extends Omit>): Span; - - /** - * Returns the current span properties as a `SpanContext`. - * @deprecated Use `toJSON()` or access the fields directly instead. - */ - toContext(): SpanContext; - - /** - * Convert the object to JSON for w. spans array info only. - * @deprecated Use `spanToTraceContext()` util function instead. - */ - getTraceContext(): TraceContext; - - /** - * Convert the object to JSON. - * @deprecated Use `spanToJSON(span)` instead. - */ - toJSON(): SpanJSON; - /** * If this is span is actually recording data. * This will return false if tracing is disabled, this span was not sampled or if the span is already finished. diff --git a/packages/types/src/transaction.ts b/packages/types/src/transaction.ts index 235ba07242db..4b05fb430565 100644 --- a/packages/types/src/transaction.ts +++ b/packages/types/src/transaction.ts @@ -37,7 +37,22 @@ export interface TransactionContext extends SpanContext { /** * Data pulled from a `sentry-trace` header */ -export type TraceparentData = Pick; +export interface TraceparentData { + /** + * Trace ID + */ + traceId?: string | undefined; + + /** + * Parent Span ID + */ + parentSpanId?: string | undefined; + + /** + * If this transaction has a parent, the parent's sampling decision + */ + parentSampled?: boolean | undefined; +} /** * Transaction "Class", inherits Span only has `setName` @@ -125,6 +140,14 @@ export interface Transaction extends Omit, Sp * @deprecated Use top-level `getDynamicSamplingContextFromSpan` instead. */ getDynamicSamplingContext(): Partial; + + /** + * Creates a new `Span` while setting the current `Span.id` as `parentSpanId`. + * Also the `sampled` decision will be inherited. + * + * @deprecated Use `startSpan()`, `startSpanManual()` or `startInactiveSpan()` instead. + */ + startChild(spanContext?: Pick>): Span; } /** diff --git a/packages/utils/src/requestdata.ts b/packages/utils/src/requestdata.ts index 19c2cbd18f4e..f6a4129fac2a 100644 --- a/packages/utils/src/requestdata.ts +++ b/packages/utils/src/requestdata.ts @@ -87,9 +87,18 @@ export function addRequestDataToTransaction( if (req.baseUrl) { transaction.setAttribute('baseUrl', req.baseUrl); } - // TODO: We need to rewrite this to a flat format? - // eslint-disable-next-line deprecation/deprecation - transaction.setData('query', extractQueryParams(req)); + + const query = extractQueryParams(req); + if (typeof query === 'string') { + transaction.setAttribute('query', query); + } else if (query) { + Object.keys(query).forEach(key => { + const val = query[key]; + if (typeof val === 'string' || typeof val === 'number') { + transaction.setAttribute(`query.${key}`, val); + } + }); + } } /** From ca903317c7ff1cfde088bdf1b2d2211c6a3021e0 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Thu, 29 Feb 2024 12:43:11 -0330 Subject: [PATCH 08/17] fix(replay): Add `errorHandler` for replayCanvas integration (#10796) `errorHandler` for `CanvasManager` was added in the latest rrweb, but was not configured in our integration. --- packages/replay-canvas/src/canvas.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/packages/replay-canvas/src/canvas.ts b/packages/replay-canvas/src/canvas.ts index 5e3f5ab1b7bd..5da590a162d7 100644 --- a/packages/replay-canvas/src/canvas.ts +++ b/packages/replay-canvas/src/canvas.ts @@ -73,7 +73,20 @@ export const _replayCanvasIntegration = ((options: Partial enableManualSnapshot, recordCanvas: true, getCanvasManager: (options: CanvasManagerOptions) => { - const manager = new CanvasManager({ ...options, enableManualSnapshot }); + const manager = new CanvasManager({ + ...options, + enableManualSnapshot, + errorHandler: (err: unknown) => { + try { + if (typeof err === 'object') { + (err as Error & { __rrweb__?: boolean }).__rrweb__ = true; + } + } catch (error) { + // ignore errors here + // this can happen if the error is frozen or does not allow mutation for other reasons + } + }, + }); canvasManagerResolve(manager); return manager; }, From bd37568395160e14353d74ae0f21baa3382e2eda Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 29 Feb 2024 17:18:16 +0000 Subject: [PATCH 09/17] fix(v8): Remove deprecated tracing config (#10870) Closes #6230 It seems this is all that is left of the deprecated tracing options! --- packages/bun/src/types.ts | 18 ------------------ packages/deno/src/types.ts | 18 ------------------ 2 files changed, 36 deletions(-) diff --git a/packages/bun/src/types.ts b/packages/bun/src/types.ts index c62e4fe320e3..933edef42627 100644 --- a/packages/bun/src/types.ts +++ b/packages/bun/src/types.ts @@ -33,24 +33,6 @@ export interface BaseBunOptions { * */ clientClass?: typeof BunClient; - // TODO (v8): Remove this in v8 - /** - * @deprecated Moved to constructor options of the `Http` and `Undici` integration. - * @example - * ```js - * Sentry.init({ - * integrations: [ - * new Sentry.Integrations.Http({ - * tracing: { - * shouldCreateSpanForRequest: (url: string) => false, - * } - * }); - * ], - * }); - * ``` - */ - shouldCreateSpanForRequest?(this: void, url: string): boolean; - /** Callback that is executed when a fatal global error occurs. */ onFatalError?(this: void, error: Error): void; } diff --git a/packages/deno/src/types.ts b/packages/deno/src/types.ts index 50310589666a..92752c5b9d0b 100644 --- a/packages/deno/src/types.ts +++ b/packages/deno/src/types.ts @@ -24,24 +24,6 @@ export interface BaseDenoOptions { /** Sets an optional server name (device name) */ serverName?: string; - // TODO (v8): Remove this in v8 - /** - * @deprecated Moved to constructor options of the `Http` and `Undici` integration. - * @example - * ```js - * Sentry.init({ - * integrations: [ - * new Sentry.Integrations.Http({ - * tracing: { - * shouldCreateSpanForRequest: (url: string) => false, - * } - * }); - * ], - * }); - * ``` - */ - shouldCreateSpanForRequest?(this: void, url: string): boolean; - /** Callback that is executed when a fatal global error occurs. */ onFatalError?(this: void, error: Error): void; } From 3cb5108fe3d228e2d4253fe4f33402db090c2c77 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Thu, 29 Feb 2024 15:21:09 -0330 Subject: [PATCH 10/17] feat(feedback): Flush replays when feedback form opens (#10567) Flush replay when the feedback form is first opened instead of at submit time We are making this change because we have noticed a lot of feedback replays only consist of the user submitting the feedback and not what they did prior to submitting feedback. This may result in false positives if users open but do not submit feedback, but this should make replays from feedback more useful. --- .../{ => hasSampling}/init.js | 0 .../hasSampling/test.ts | 107 ++++++++++++++++++ .../feedback/captureFeedbackAndReplay/test.ts | 91 --------------- packages/feedback/src/integration.ts | 10 +- packages/feedback/src/widget/createWidget.ts | 21 +++- .../replay/src/util/addGlobalListeners.ts | 2 - 6 files changed, 132 insertions(+), 99 deletions(-) rename dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/{ => hasSampling}/init.js (100%) create mode 100644 dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts delete mode 100644 dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/test.ts diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/init.js b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/init.js similarity index 100% rename from dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/init.js rename to dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/init.js diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts new file mode 100644 index 000000000000..6868caf99545 --- /dev/null +++ b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/hasSampling/test.ts @@ -0,0 +1,107 @@ +import { expect } from '@playwright/test'; + +import { sentryTest } from '../../../../utils/fixtures'; +import { envelopeRequestParser, getEnvelopeType } from '../../../../utils/helpers'; +import { getCustomRecordingEvents, getReplayEvent, waitForReplayRequest } from '../../../../utils/replayHelpers'; + +sentryTest( + 'should capture feedback (@sentry-internal/feedback import)', + async ({ forceFlushReplay, getLocalTestPath, page }) => { + if (process.env.PW_BUNDLE) { + sentryTest.skip(); + } + + const reqPromise0 = waitForReplayRequest(page, 0); + const reqPromise1 = waitForReplayRequest(page, 1); + const reqPromise2 = waitForReplayRequest(page, 2); + const feedbackRequestPromise = page.waitForResponse(res => { + const req = res.request(); + + const postData = req.postData(); + if (!postData) { + return false; + } + + try { + return getEnvelopeType(req) === 'feedback'; + } catch (err) { + return false; + } + }); + + await page.route('https://dsn.ingest.sentry.io/**/*', route => { + return route.fulfill({ + status: 200, + contentType: 'application/json', + body: JSON.stringify({ id: 'test-id' }), + }); + }); + + const url = await getLocalTestPath({ testDir: __dirname }); + + const [, , replayReq0] = await Promise.all([page.goto(url), page.getByText('Report a Bug').click(), reqPromise0]); + + // Inputs are slow, these need to be serial + await page.locator('[name="name"]').fill('Jane Doe'); + await page.locator('[name="email"]').fill('janedoe@example.org'); + await page.locator('[name="message"]').fill('my example feedback'); + + // Force flush here, as inputs are slow and can cause click event to be in unpredictable segments + await Promise.all([forceFlushReplay(), reqPromise1]); + + const [, feedbackResp, replayReq2] = await Promise.all([ + page.getByLabel('Send Bug Report').click(), + feedbackRequestPromise, + reqPromise2, + ]); + + const feedbackEvent = envelopeRequestParser(feedbackResp.request()); + const replayEvent = getReplayEvent(replayReq0); + // Feedback breadcrumb is on second segment because we flush when "Report a Bug" is clicked + // And then the breadcrumb is sent when feedback form is submitted + const { breadcrumbs } = getCustomRecordingEvents(replayReq2); + + expect(breadcrumbs).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + category: 'sentry.feedback', + data: { feedbackId: expect.any(String) }, + timestamp: expect.any(Number), + type: 'default', + }), + ]), + ); + + expect(feedbackEvent).toEqual({ + type: 'feedback', + breadcrumbs: expect.any(Array), + contexts: { + feedback: { + contact_email: 'janedoe@example.org', + message: 'my example feedback', + name: 'Jane Doe', + replay_id: replayEvent.event_id, + source: 'widget', + url: expect.stringContaining('/dist/index.html'), + }, + }, + level: 'info', + timestamp: expect.any(Number), + event_id: expect.stringMatching(/\w{32}/), + environment: 'production', + sdk: { + integrations: expect.arrayContaining(['Feedback']), + version: expect.any(String), + name: 'sentry.javascript.browser', + packages: expect.anything(), + }, + request: { + url: expect.stringContaining('/dist/index.html'), + headers: { + 'User-Agent': expect.stringContaining(''), + }, + }, + platform: 'javascript', + }); + }, +); diff --git a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/test.ts b/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/test.ts deleted file mode 100644 index 057b5d43a1c8..000000000000 --- a/dev-packages/browser-integration-tests/suites/feedback/captureFeedbackAndReplay/test.ts +++ /dev/null @@ -1,91 +0,0 @@ -import { expect } from '@playwright/test'; - -import { sentryTest } from '../../../utils/fixtures'; -import { envelopeRequestParser, getEnvelopeType } from '../../../utils/helpers'; -import { getCustomRecordingEvents, getReplayEvent, waitForReplayRequest } from '../../../utils/replayHelpers'; - -sentryTest('should capture feedback (@sentry-internal/feedback import)', async ({ getLocalTestPath, page }) => { - if (process.env.PW_BUNDLE) { - sentryTest.skip(); - } - - const reqPromise0 = waitForReplayRequest(page, 0); - const feedbackRequestPromise = page.waitForResponse(res => { - const req = res.request(); - - const postData = req.postData(); - if (!postData) { - return false; - } - - try { - return getEnvelopeType(req) === 'feedback'; - } catch (err) { - return false; - } - }); - - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - return route.fulfill({ - status: 200, - contentType: 'application/json', - body: JSON.stringify({ id: 'test-id' }), - }); - }); - - const url = await getLocalTestPath({ testDir: __dirname }); - - await page.goto(url); - await page.getByText('Report a Bug').click(); - await page.locator('[name="name"]').fill('Jane Doe'); - await page.locator('[name="email"]').fill('janedoe@example.org'); - await page.locator('[name="message"]').fill('my example feedback'); - await page.getByLabel('Send Bug Report').click(); - - const [feedbackResp, replayReq] = await Promise.all([feedbackRequestPromise, reqPromise0]); - - const feedbackEvent = envelopeRequestParser(feedbackResp.request()); - const replayEvent = getReplayEvent(replayReq); - const { breadcrumbs } = getCustomRecordingEvents(replayReq); - - expect(breadcrumbs).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - category: 'sentry.feedback', - data: { feedbackId: expect.any(String) }, - }), - ]), - ); - - expect(feedbackEvent).toEqual({ - type: 'feedback', - breadcrumbs: expect.any(Array), - contexts: { - feedback: { - contact_email: 'janedoe@example.org', - message: 'my example feedback', - name: 'Jane Doe', - replay_id: replayEvent.event_id, - source: 'widget', - url: expect.stringContaining('/dist/index.html'), - }, - }, - level: 'info', - timestamp: expect.any(Number), - event_id: expect.stringMatching(/\w{32}/), - environment: 'production', - sdk: { - integrations: expect.arrayContaining(['Feedback']), - version: expect.any(String), - name: 'sentry.javascript.browser', - packages: expect.anything(), - }, - request: { - url: expect.stringContaining('/dist/index.html'), - headers: { - 'User-Agent': expect.stringContaining(''), - }, - }, - platform: 'javascript', - }); -}); diff --git a/packages/feedback/src/integration.ts b/packages/feedback/src/integration.ts index 501f844abeaa..b70bf008d24d 100644 --- a/packages/feedback/src/integration.ts +++ b/packages/feedback/src/integration.ts @@ -79,17 +79,17 @@ export class Feedback implements Integration { private _hasInsertedActorStyles: boolean; public constructor({ + autoInject = true, id = 'sentry-feedback', + isEmailRequired = false, + isNameRequired = false, showBranding = true, - autoInject = true, showEmail = true, showName = true, useSentryUser = { email: 'email', name: 'username', }, - isEmailRequired = false, - isNameRequired = false, themeDark, themeLight, @@ -123,9 +123,9 @@ export class Feedback implements Integration { this._hasInsertedActorStyles = false; this.options = { - id, - showBranding, autoInject, + showBranding, + id, isEmailRequired, isNameRequired, showEmail, diff --git a/packages/feedback/src/widget/createWidget.ts b/packages/feedback/src/widget/createWidget.ts index b5e414803121..05b52ca64725 100644 --- a/packages/feedback/src/widget/createWidget.ts +++ b/packages/feedback/src/widget/createWidget.ts @@ -1,4 +1,4 @@ -import { getCurrentScope } from '@sentry/core'; +import { getClient, getCurrentScope } from '@sentry/core'; import { logger } from '@sentry/utils'; import type { FeedbackFormData, FeedbackInternalOptions, FeedbackWidget } from '../types'; @@ -9,6 +9,8 @@ import type { DialogComponent } from './Dialog'; import { Dialog } from './Dialog'; import { SuccessMessage } from './SuccessMessage'; +import { DEBUG_BUILD } from '../debug-build'; + interface CreateWidgetParams { /** * Shadow DOM to append to @@ -124,6 +126,21 @@ export function createWidget({ } } + /** + * Internal handler when dialog is opened + */ + function handleOpenDialog(): void { + // Flush replay if integration exists + const client = getClient(); + const replay = client && client.getIntegrationByName<{ name: string; flush: () => Promise }>('Replay'); + if (!replay) { + return; + } + replay.flush().catch(err => { + DEBUG_BUILD && logger.error(err); + }); + } + /** * Displays the default actor */ @@ -156,6 +173,7 @@ export function createWidget({ if (options.onFormOpen) { options.onFormOpen(); } + handleOpenDialog(); return; } @@ -208,6 +226,7 @@ export function createWidget({ if (options.onFormOpen) { options.onFormOpen(); } + handleOpenDialog(); } catch (err) { // TODO: Error handling? logger.error(err); diff --git a/packages/replay/src/util/addGlobalListeners.ts b/packages/replay/src/util/addGlobalListeners.ts index 9b57e7dafec8..a5900fdea696 100644 --- a/packages/replay/src/util/addGlobalListeners.ts +++ b/packages/replay/src/util/addGlobalListeners.ts @@ -59,8 +59,6 @@ export function addGlobalListeners(replay: ReplayContainer): void { const replayId = replay.getSessionId(); if (options && options.includeReplay && replay.isEnabled() && replayId) { // This should never reject - // eslint-disable-next-line @typescript-eslint/no-floating-promises - replay.flush(); if (feedbackEvent.contexts && feedbackEvent.contexts.feedback) { feedbackEvent.contexts.feedback.replay_id = replayId; } From 16ed61377cb83f64d8d59ed0682010b2299a6374 Mon Sep 17 00:00:00 2001 From: Daniel Griesser Date: Fri, 1 Mar 2024 09:43:49 +0100 Subject: [PATCH 11/17] feat: Ignore ResizeObserver and undefined error (#10845) We want to filter out `ResizeObserver` error since they are not actionable and have no stacktrace and break transaction status. ref https://stackoverflow.com/a/77680580/1139707 Also, I added to filter out undefined errors - here are two events to Sentry to represent this. undefined: https://sentry.sentry.io/issues/3611187513/events/46ed8c398c234ff89baee87c5c341844/ ResizeObserver: https://sentry.sentry.io/issues/3611187513/events/48f25ea9dfbf4bd0b84a18982ee73362/ --- packages/core/src/integrations/inboundfilters.ts | 6 +++++- .../test/lib/integrations/inboundfilters.test.ts | 16 ++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/packages/core/src/integrations/inboundfilters.ts b/packages/core/src/integrations/inboundfilters.ts index 7d42d57f81ed..ac89c7d222c6 100644 --- a/packages/core/src/integrations/inboundfilters.ts +++ b/packages/core/src/integrations/inboundfilters.ts @@ -6,7 +6,11 @@ import { convertIntegrationFnToClass, defineIntegration } from '../integration'; // "Script error." is hard coded into browsers for errors that it can't read. // this is the result of a script being pulled in from an external domain and CORS. -const DEFAULT_IGNORE_ERRORS = [/^Script error\.?$/, /^Javascript error: Script error\.? on line 0$/]; +const DEFAULT_IGNORE_ERRORS = [ + /^Script error\.?$/, + /^Javascript error: Script error\.? on line 0$/, + /^ResizeObserver loop completed with undelivered notifications.$/, +]; /** Options for the InboundFilters integration */ export interface InboundFiltersOptions { diff --git a/packages/core/test/lib/integrations/inboundfilters.test.ts b/packages/core/test/lib/integrations/inboundfilters.test.ts index 012c3f5f5f0d..f3c29b0398d2 100644 --- a/packages/core/test/lib/integrations/inboundfilters.test.ts +++ b/packages/core/test/lib/integrations/inboundfilters.test.ts @@ -188,6 +188,17 @@ const SCRIPT_ERROR_EVENT: Event = { }, }; +const RESIZEOBSERVER_EVENT: Event = { + exception: { + values: [ + { + type: 'Error', + value: 'ResizeObserver loop completed with undelivered notifications.', + }, + ], + }, +}; + const MALFORMED_EVENT: Event = { exception: { values: [ @@ -294,6 +305,11 @@ describe('InboundFilters', () => { expect(eventProcessor(SCRIPT_ERROR_EVENT, {})).toBe(null); }); + it('uses default filters ResizeObserver', () => { + const eventProcessor = createInboundFiltersEventProcessor(); + expect(eventProcessor(RESIZEOBSERVER_EVENT, {})).toBe(null); + }); + it('filters on last exception when multiple present', () => { const eventProcessor = createInboundFiltersEventProcessor({ ignoreErrors: ['incorrect type given for parameter `chewToy`'], From a845a0e1a512ec5fc2f13d4ada7386686bffb1d6 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 1 Mar 2024 08:47:41 +0000 Subject: [PATCH 12/17] feat(profiling-node): Expose `nodeProfilingIntegration` (#10868) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @JonasBa when you got some time, could you look into actually rewriting this to the new style? Right now a lot of the tests depend on the non-hook based version etc. and I'm not 100% clear on what can/cannot be removed there 😅 The end result should be just having a single `nodeProfilingIntegration` that receives the client in `setup(client)` and sets up hooks etc in there! --- MIGRATION.md | 82 ++++++++++--------- packages/profiling-node/README.md | 4 +- packages/profiling-node/src/index.ts | 6 +- packages/profiling-node/src/integration.ts | 23 +++++- .../test/hubextensions.hub.test.ts | 2 + packages/profiling-node/test/index.test.ts | 1 + .../profiling-node/test/integration.test.ts | 9 ++ 7 files changed, 83 insertions(+), 44 deletions(-) diff --git a/MIGRATION.md b/MIGRATION.md index 30206739b42e..aae2f3ee4292 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -740,46 +740,48 @@ The following list shows how integrations should be migrated: ### List of integrations and their replacements -| Old | New | Packages | -| ---------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------- | -| `new BrowserTracing()` | `browserTracingIntegration()` | `@sentry/browser` | -| `new InboundFilters()` | `inboundFiltersIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` | -| `new FunctionToString()` | `functionToStringIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` | -| `new LinkedErrors()` | `linkedErrorsIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` | -| `new ModuleMetadata()` | `moduleMetadataIntegration()` | `@sentry/core`, `@sentry/browser` | -| `new RequestData()` | `requestDataIntegration()` | `@sentry/core`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` | -| `new Wasm() ` | `wasmIntegration()` | `@sentry/wasm` | -| `new Replay()` | `replayIntegration()` | `@sentry/browser` | -| `new ReplayCanvas()` | `replayCanvasIntegration()` | `@sentry/browser` | -| `new Feedback()` | `feedbackIntegration()` | `@sentry/browser` | -| `new CaptureConsole()` | `captureConsoleIntegration()` | `@sentry/integrations` | -| `new Debug()` | `debugIntegration()` | `@sentry/integrations` | -| `new Dedupe()` | `dedupeIntegration()` | `@sentry/browser`, `@sentry/integrations`, `@sentry/deno` | -| `new ExtraErrorData()` | `extraErrorDataIntegration()` | `@sentry/integrations` | -| `new ReportingObserver()` | `reportingObserverIntegration()` | `@sentry/integrations` | -| `new RewriteFrames()` | `rewriteFramesIntegration()` | `@sentry/integrations` | -| `new SessionTiming()` | `sessionTimingIntegration()` | `@sentry/integrations` | -| `new HttpClient()` | `httpClientIntegration()` | `@sentry/integrations` | -| `new ContextLines()` | `contextLinesIntegration()` | `@sentry/integrations`, `@sentry/node`, `@sentry/deno`, `@sentry/bun` | -| `new Breadcrumbs()` | `breadcrumbsIntegration()` | `@sentry/browser`, `@sentry/deno` | -| `new GlobalHandlers()` | `globalHandlersIntegration()` | `@sentry/browser` , `@sentry/deno` | -| `new HttpContext()` | `httpContextIntegration()` | `@sentry/browser` | -| `new TryCatch()` | `browserApiErrorsIntegration()` | `@sentry/browser`, `@sentry/deno` | -| `new VueIntegration()` | `vueIntegration()` | `@sentry/vue` | -| `new DenoContext()` | `denoContextIntegration()` | `@sentry/deno` | -| `new DenoCron()` | `denoCronIntegration()` | `@sentry/deno` | -| `new NormalizePaths()` | `normalizePathsIntegration()` | `@sentry/deno` | -| `new Console()` | `consoleIntegration()` | `@sentry/node` | -| `new Context()` | `nodeContextIntegration()` | `@sentry/node` | -| `new Modules()` | `modulesIntegration()` | `@sentry/node` | -| `new OnUncaughtException()` | `onUncaughtExceptionIntegration()` | `@sentry/node` | -| `new OnUnhandledRejection()` | `onUnhandledRejectionIntegration()` | `@sentry/node` | -| `new LocalVariables()` | `localVariablesIntegration()` | `@sentry/node` | -| `new Spotlight()` | `spotlightIntegration()` | `@sentry/node` | -| `new Anr()` | `anrIntegration()` | `@sentry/node` | -| `new Hapi()` | `hapiIntegration()` | `@sentry/node` | -| `new Undici()` | `nativeNodeFetchIntegration()` | `@sentry/node` | -| `new Http()` | `httpIntegration()` | `@sentry/node` | +| Old | New | Packages | +| ----------------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------- | +| `new BrowserTracing()` | `browserTracingIntegration()` | `@sentry/browser` | +| `new InboundFilters()` | `inboundFiltersIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` | +| `new FunctionToString()` | `functionToStringIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` | +| `new LinkedErrors()` | `linkedErrorsIntegration()` | `@sentry/core`, `@sentry/browser`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` | +| `new ModuleMetadata()` | `moduleMetadataIntegration()` | `@sentry/core`, `@sentry/browser` | +| `new RequestData()` | `requestDataIntegration()` | `@sentry/core`, `@sentry/node`, `@sentry/deno`, `@sentry/bun`, `@sentry/vercel-edge` | +| `new Wasm() ` | `wasmIntegration()` | `@sentry/wasm` | +| `new Replay()` | `replayIntegration()` | `@sentry/browser` | +| `new ReplayCanvas()` | `replayCanvasIntegration()` | `@sentry/browser` | +| `new Feedback()` | `feedbackIntegration()` | `@sentry/browser` | +| `new CaptureConsole()` | `captureConsoleIntegration()` | `@sentry/integrations` | +| `new Debug()` | `debugIntegration()` | `@sentry/integrations` | +| `new Dedupe()` | `dedupeIntegration()` | `@sentry/browser`, `@sentry/integrations`, `@sentry/deno` | +| `new ExtraErrorData()` | `extraErrorDataIntegration()` | `@sentry/integrations` | +| `new ReportingObserver()` | `reportingObserverIntegration()` | `@sentry/integrations` | +| `new RewriteFrames()` | `rewriteFramesIntegration()` | `@sentry/integrations` | +| `new SessionTiming()` | `sessionTimingIntegration()` | `@sentry/integrations` | +| `new HttpClient()` | `httpClientIntegration()` | `@sentry/integrations` | +| `new ContextLines()` | `contextLinesIntegration()` | `@sentry/integrations`, `@sentry/node`, `@sentry/deno`, `@sentry/bun` | +| `new Breadcrumbs()` | `breadcrumbsIntegration()` | `@sentry/browser`, `@sentry/deno` | +| `new GlobalHandlers()` | `globalHandlersIntegration()` | `@sentry/browser` , `@sentry/deno` | +| `new HttpContext()` | `httpContextIntegration()` | `@sentry/browser` | +| `new TryCatch()` | `browserApiErrorsIntegration()` | `@sentry/browser`, `@sentry/deno` | +| `new VueIntegration()` | `vueIntegration()` | `@sentry/vue` | +| `new DenoContext()` | `denoContextIntegration()` | `@sentry/deno` | +| `new DenoCron()` | `denoCronIntegration()` | `@sentry/deno` | +| `new NormalizePaths()` | `normalizePathsIntegration()` | `@sentry/deno` | +| `new Console()` | `consoleIntegration()` | `@sentry/node` | +| `new Context()` | `nodeContextIntegration()` | `@sentry/node` | +| `new Modules()` | `modulesIntegration()` | `@sentry/node` | +| `new OnUncaughtException()` | `onUncaughtExceptionIntegration()` | `@sentry/node` | +| `new OnUnhandledRejection()` | `onUnhandledRejectionIntegration()` | `@sentry/node` | +| `new LocalVariables()` | `localVariablesIntegration()` | `@sentry/node` | +| `new Spotlight()` | `spotlightIntegration()` | `@sentry/node` | +| `new Anr()` | `anrIntegration()` | `@sentry/node` | +| `new Hapi()` | `hapiIntegration()` | `@sentry/node` | +| `new Undici()` | `nativeNodeFetchIntegration()` | `@sentry/node` | +| `new Http()` | `httpIntegration()` | `@sentry/node` | +| `new ProfilingIntegration()` | `nodeProfilingIntegration()` | `@sentry/profiling-node` | +| `new BrowserProfilingIntegration()` | `browserProfilingIntegration()` | `@sentry/browser` | ## Deprecate `hub.bindClient()` and `makeMain()` diff --git a/packages/profiling-node/README.md b/packages/profiling-node/README.md index 3facb6cd7541..e496ba5f96a0 100644 --- a/packages/profiling-node/README.md +++ b/packages/profiling-node/README.md @@ -27,14 +27,14 @@ npm install --save @sentry/node @sentry/profiling-node ```javascript import * as Sentry from '@sentry/node'; -import { ProfilingIntegration } from '@sentry/profiling-node'; +import { nodeProfilingIntegration } from '@sentry/profiling-node'; Sentry.init({ dsn: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', debug: true, tracesSampleRate: 1, profilesSampleRate: 1, // Set profiling sampling rate. - integrations: [new ProfilingIntegration()], + integrations: [nodeProfilingIntegration()], }); ``` diff --git a/packages/profiling-node/src/index.ts b/packages/profiling-node/src/index.ts index fee7c526929d..086fbb86de58 100644 --- a/packages/profiling-node/src/index.ts +++ b/packages/profiling-node/src/index.ts @@ -1 +1,5 @@ -export { ProfilingIntegration } from './integration'; +export { + // eslint-disable-next-line deprecation/deprecation + ProfilingIntegration, + nodeProfilingIntegration, +} from './integration'; diff --git a/packages/profiling-node/src/integration.ts b/packages/profiling-node/src/integration.ts index 202578f49d88..90c3ec6d7ffb 100644 --- a/packages/profiling-node/src/integration.ts +++ b/packages/profiling-node/src/integration.ts @@ -1,6 +1,14 @@ import { spanToJSON } from '@sentry/core'; import type { NodeClient } from '@sentry/node-experimental'; -import type { Event, EventProcessor, Hub, Integration, Transaction } from '@sentry/types'; +import type { + Event, + EventProcessor, + Hub, + Integration, + IntegrationFn, + IntegrationFnResult, + Transaction, +} from '@sentry/types'; import { logger } from '@sentry/utils'; @@ -40,6 +48,8 @@ function addToProfileQueue(profile: RawThreadCpuProfile): void { * and inspect each event to see if it is a transaction event and if that transaction event * contains a profile on it's metadata. If that is the case, we create a profiling event envelope * and delete the profile from the transaction metadata. + * + * @deprecated Use `nodeProfilingIntegration` instead. */ export class ProfilingIntegration implements Integration { /** @@ -245,3 +255,14 @@ export class ProfilingIntegration implements Integration { return maybeRemoveProfileFromSdkMetadata(event); } } + +/** + * We need this integration in order to send data to Sentry. We hook into the event processor + * and inspect each event to see if it is a transaction event and if that transaction event + * contains a profile on it's metadata. If that is the case, we create a profiling event envelope + * and delete the profile from the transaction metadata. + */ +export const nodeProfilingIntegration = (() => { + // eslint-disable-next-line deprecation/deprecation + return new ProfilingIntegration() as unknown as IntegrationFnResult; +}) satisfies IntegrationFn; diff --git a/packages/profiling-node/test/hubextensions.hub.test.ts b/packages/profiling-node/test/hubextensions.hub.test.ts index b73592323044..755923f62af7 100644 --- a/packages/profiling-node/test/hubextensions.hub.test.ts +++ b/packages/profiling-node/test/hubextensions.hub.test.ts @@ -7,6 +7,7 @@ import { CpuProfilerBindings } from '../src/cpu_profiler'; import { ProfilingIntegration } from '../src/index'; function makeClientWithoutHooks(): [Sentry.NodeClient, Transport] { + // eslint-disable-next-line deprecation/deprecation const integration = new ProfilingIntegration(); const transport = Sentry.makeNodeTransport({ url: 'https://7fa19397baaf433f919fbe02228d5470@o1137848.ingest.sentry.io/6625302', @@ -41,6 +42,7 @@ function makeClientWithoutHooks(): [Sentry.NodeClient, Transport] { } function makeClientWithHooks(): [Sentry.NodeClient, Transport] { + // eslint-disable-next-line deprecation/deprecation const integration = new ProfilingIntegration(); const client = new Sentry.NodeClient({ stackParser: Sentry.defaultStackParser, diff --git a/packages/profiling-node/test/index.test.ts b/packages/profiling-node/test/index.test.ts index b29c21bb1f23..3a59d4bd53f4 100644 --- a/packages/profiling-node/test/index.test.ts +++ b/packages/profiling-node/test/index.test.ts @@ -23,6 +23,7 @@ function makeStaticTransport(): MockTransport { } function makeClientWithoutHooks(): [Sentry.NodeClient, MockTransport] { + // eslint-disable-next-line deprecation/deprecation const integration = new ProfilingIntegration(); const transport = makeStaticTransport(); const client = new Sentry.NodeClient({ diff --git a/packages/profiling-node/test/integration.test.ts b/packages/profiling-node/test/integration.test.ts index 8f336600fa84..7efd1cd03878 100644 --- a/packages/profiling-node/test/integration.test.ts +++ b/packages/profiling-node/test/integration.test.ts @@ -43,10 +43,12 @@ describe('ProfilingIntegration', () => { jest.clearAllMocks(); }); it('has a name', () => { + // eslint-disable-next-line deprecation/deprecation expect(new ProfilingIntegration().name).toBe('ProfilingIntegration'); }); it('stores a reference to getCurrentHub', () => { + // eslint-disable-next-line deprecation/deprecation const integration = new ProfilingIntegration(); const getCurrentHub = jest.fn().mockImplementation(() => { @@ -66,6 +68,7 @@ describe('ProfilingIntegration', () => { send: jest.fn().mockImplementation(() => Promise.resolve()), flush: jest.fn().mockImplementation(() => Promise.resolve()), }; + // eslint-disable-next-line deprecation/deprecation const integration = new ProfilingIntegration(); const getCurrentHub = jest.fn((): Hub => { @@ -100,6 +103,7 @@ describe('ProfilingIntegration', () => { it('when Hub.getClient returns undefined', async () => { const logSpy = jest.spyOn(logger, 'log'); + // eslint-disable-next-line deprecation/deprecation const integration = new ProfilingIntegration(); const getCurrentHub = jest.fn((): Hub => { @@ -115,6 +119,7 @@ describe('ProfilingIntegration', () => { }); it('when getDsn returns undefined', async () => { const logSpy = jest.spyOn(logger, 'log'); + // eslint-disable-next-line deprecation/deprecation const integration = new ProfilingIntegration(); const getCurrentHub = jest.fn((): Hub => { @@ -136,6 +141,7 @@ describe('ProfilingIntegration', () => { }); it('when getTransport returns undefined', async () => { const logSpy = jest.spyOn(logger, 'log'); + // eslint-disable-next-line deprecation/deprecation const integration = new ProfilingIntegration(); const getCurrentHub = jest.fn((): Hub => { @@ -165,6 +171,7 @@ describe('ProfilingIntegration', () => { send: jest.fn().mockImplementation(() => Promise.resolve()), flush: jest.fn().mockImplementation(() => Promise.resolve()), }; + // eslint-disable-next-line deprecation/deprecation const integration = new ProfilingIntegration(); const getCurrentHub = jest.fn((): Hub => { @@ -198,6 +205,7 @@ describe('ProfilingIntegration', () => { send: jest.fn().mockImplementation(() => Promise.resolve()), flush: jest.fn().mockImplementation(() => Promise.resolve()), }; + // eslint-disable-next-line deprecation/deprecation const integration = new ProfilingIntegration(); const emitter = new EventEmitter(); @@ -233,6 +241,7 @@ describe('ProfilingIntegration', () => { send: jest.fn().mockImplementation(() => Promise.resolve()), flush: jest.fn().mockImplementation(() => Promise.resolve()), }; + // eslint-disable-next-line deprecation/deprecation const integration = new ProfilingIntegration(); const emitter = new EventEmitter(); From 652b6214f05aa095f2c51a343496c2e0470f9667 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 1 Mar 2024 09:00:36 +0000 Subject: [PATCH 13/17] feat: Ensure `withActiveSpan` is exported everywhere (#10878) We forgot to do that, apparently :grimace: --- packages/astro/src/index.server.ts | 1 + packages/bun/src/index.ts | 2 +- packages/node-experimental/src/index.ts | 14 ++++++++++++-- packages/node/src/index.ts | 2 +- packages/remix/src/index.server.ts | 1 + packages/serverless/src/index.ts | 2 +- packages/sveltekit/src/server/index.ts | 1 + packages/vercel-edge/src/index.ts | 1 + 8 files changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index d3aba19e9ac6..39012cc546b3 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -65,6 +65,7 @@ export { startSpan, startInactiveSpan, startSpanManual, + withActiveSpan, continueTrace, cron, parameterize, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index fe00c1eef4b9..feb114d4723f 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -67,6 +67,7 @@ export { startSpan, startInactiveSpan, startSpanManual, + withActiveSpan, continueTrace, metricsDefault as metrics, functionToStringIntegration, @@ -83,7 +84,6 @@ export { startSession, captureSession, endSession, - withActiveSpan, } from '@sentry/core'; export type { SpanStatusType } from '@sentry/core'; export { diff --git a/packages/node-experimental/src/index.ts b/packages/node-experimental/src/index.ts index b8c12b6ba3f9..9f0712cac96a 100644 --- a/packages/node-experimental/src/index.ts +++ b/packages/node-experimental/src/index.ts @@ -40,9 +40,19 @@ export { cron } from './cron'; export type { Span, NodeOptions } from './types'; -export { startSpan, startSpanManual, startInactiveSpan, getActiveSpan, withActiveSpan } from '@sentry/opentelemetry'; +export { + startSpan, + startSpanManual, + startInactiveSpan, + getActiveSpan, + withActiveSpan, +} from '@sentry/opentelemetry'; -export { addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData } from '@sentry/utils'; +export { + addRequestDataToEvent, + DEFAULT_USER_INCLUDES, + extractRequestData, +} from '@sentry/utils'; export { addBreadcrumb, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index bfe015a1d593..8f4e1a9ee87c 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -66,6 +66,7 @@ export { startSpan, startInactiveSpan, startSpanManual, + withActiveSpan, continueTrace, parameterize, functionToStringIntegration, @@ -76,7 +77,6 @@ export { startSession, captureSession, endSession, - withActiveSpan, } from '@sentry/core'; export { diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index 86c74cc5c052..3b67422f4ae5 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -76,6 +76,7 @@ export { startSpan, startSpanManual, startInactiveSpan, + withActiveSpan, continueTrace, isInitialized, cron, diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index bc709a402961..5e153ae7f4c5 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -69,6 +69,7 @@ export { startSpan, startInactiveSpan, startSpanManual, + withActiveSpan, continueTrace, parameterize, requestDataIntegration, @@ -96,7 +97,6 @@ export { startSession, captureSession, endSession, - withActiveSpan, } from '@sentry/node-experimental'; export { diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index e77b6fecec34..2f1126ea329f 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -71,6 +71,7 @@ export { startSpan, startInactiveSpan, startSpanManual, + withActiveSpan, continueTrace, cron, parameterize, diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index f387bef70369..f09c61bdac90 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -66,6 +66,7 @@ export { startSpan, startInactiveSpan, startSpanManual, + withActiveSpan, continueTrace, metrics, functionToStringIntegration, From 30cadf9300f68b47ea8c337be7460942bf81b20c Mon Sep 17 00:00:00 2001 From: Sigrid Huemer <32902192+s1gr1d@users.noreply.github.com> Date: Fri, 1 Mar 2024 10:39:01 +0100 Subject: [PATCH 14/17] ref(deno): Remove `@sentry/browser` dependency from `@sentry/deno` (#10867) Adds the breadcrumbsIntegration (without xhr, dom, history) to deno without importing that from browser. --- packages/deno/package.json | 1 - packages/deno/src/index.ts | 2 +- packages/deno/src/integrations/breadcrumbs.ts | 171 ++++++++++++++++++ packages/deno/src/sdk.ts | 16 +- 4 files changed, 180 insertions(+), 10 deletions(-) create mode 100644 packages/deno/src/integrations/breadcrumbs.ts diff --git a/packages/deno/package.json b/packages/deno/package.json index 6ba7a746dad7..21d76bbed255 100644 --- a/packages/deno/package.json +++ b/packages/deno/package.json @@ -13,7 +13,6 @@ }, "files": ["index.mjs", "index.mjs.map", "index.d.ts"], "dependencies": { - "@sentry/browser": "8.0.0-alpha.0", "@sentry/core": "8.0.0-alpha.0", "@sentry/types": "8.0.0-alpha.0", "@sentry/utils": "8.0.0-alpha.0" diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 067cfd8a1599..50a422f776ab 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -95,7 +95,6 @@ export { init, } from './sdk'; -export { breadcrumbsIntegration } from '@sentry/browser'; import { Integrations as CoreIntegrations } from '@sentry/core'; export { denoContextIntegration } from './integrations/context'; @@ -103,6 +102,7 @@ export { globalHandlersIntegration } from './integrations/globalhandlers'; export { normalizePathsIntegration } from './integrations/normalizepaths'; export { contextLinesIntegration } from './integrations/contextlines'; export { denoCronIntegration } from './integrations/deno-cron'; +export { breadcrumbsIntegration } from './integrations/breadcrumbs'; import * as DenoIntegrations from './integrations'; diff --git a/packages/deno/src/integrations/breadcrumbs.ts b/packages/deno/src/integrations/breadcrumbs.ts new file mode 100644 index 000000000000..886d941d843f --- /dev/null +++ b/packages/deno/src/integrations/breadcrumbs.ts @@ -0,0 +1,171 @@ +import { addBreadcrumb, defineIntegration, getClient } from '@sentry/core'; +import type { Client, Event as SentryEvent, HandlerDataConsole, HandlerDataFetch, IntegrationFn } from '@sentry/types'; +import type { FetchBreadcrumbData, FetchBreadcrumbHint } from '@sentry/types/build/types/breadcrumb'; +import { + addConsoleInstrumentationHandler, + addFetchInstrumentationHandler, + getEventDescription, + safeJoin, + severityLevelFromString, +} from '@sentry/utils'; + +interface BreadcrumbsOptions { + console: boolean; + fetch: boolean; + sentry: boolean; +} + +const INTEGRATION_NAME = 'Breadcrumbs'; + +const _breadcrumbsIntegration = ((options: Partial = {}) => { + const _options = { + console: true, + fetch: true, + sentry: true, + ...options, + }; + + return { + name: INTEGRATION_NAME, + setup(client) { + if (_options.console) { + addConsoleInstrumentationHandler(_getConsoleBreadcrumbHandler(client)); + } + if (_options.fetch) { + addFetchInstrumentationHandler(_getFetchBreadcrumbHandler(client)); + } + if (_options.sentry) { + client.on('beforeSendEvent', _getSentryBreadcrumbHandler(client)); + } + }, + }; +}) satisfies IntegrationFn; + +/** + * This breadcrumbsIntegration is almost the same as the one from @sentry/browser. + * The Deno-version does not support browser-specific APIs like dom, xhr and history. + */ +export const breadcrumbsIntegration = defineIntegration(_breadcrumbsIntegration); + +/** + * Adds a breadcrumb for Sentry events or transactions if this option is enabled. + * + */ +function _getSentryBreadcrumbHandler(client: Client): (event: SentryEvent) => void { + return function addSentryBreadcrumb(event: SentryEvent): void { + if (getClient() !== client) { + return; + } + + addBreadcrumb( + { + category: `sentry.${event.type === 'transaction' ? 'transaction' : 'event'}`, + event_id: event.event_id, + level: event.level, + message: getEventDescription(event), + }, + { + event, + }, + ); + }; +} + +/** + * Creates breadcrumbs from console API calls + */ +function _getConsoleBreadcrumbHandler(client: Client): (handlerData: HandlerDataConsole) => void { + return function _consoleBreadcrumb(handlerData: HandlerDataConsole): void { + if (getClient() !== client) { + return; + } + + const breadcrumb = { + category: 'console', + data: { + arguments: handlerData.args, + logger: 'console', + }, + level: severityLevelFromString(handlerData.level), + message: safeJoin(handlerData.args, ' '), + }; + + if (handlerData.level === 'assert') { + if (handlerData.args[0] === false) { + breadcrumb.message = `Assertion failed: ${safeJoin(handlerData.args.slice(1), ' ') || 'console.assert'}`; + breadcrumb.data.arguments = handlerData.args.slice(1); + } else { + // Don't capture a breadcrumb for passed assertions + return; + } + } + + addBreadcrumb(breadcrumb, { + input: handlerData.args, + level: handlerData.level, + }); + }; +} + +/** + * Creates breadcrumbs from fetch API calls + */ +function _getFetchBreadcrumbHandler(client: Client): (handlerData: HandlerDataFetch) => void { + return function _fetchBreadcrumb(handlerData: HandlerDataFetch): void { + if (getClient() !== client) { + return; + } + + const { startTimestamp, endTimestamp } = handlerData; + + // We only capture complete fetch requests + if (!endTimestamp) { + return; + } + + if (handlerData.fetchData.url.match(/sentry_key/) && handlerData.fetchData.method === 'POST') { + // We will not create breadcrumbs for fetch requests that contain `sentry_key` (internal sentry requests) + return; + } + + if (handlerData.error) { + const data: FetchBreadcrumbData = handlerData.fetchData; + const hint: FetchBreadcrumbHint = { + data: handlerData.error, + input: handlerData.args, + startTimestamp, + endTimestamp, + }; + + addBreadcrumb( + { + category: 'fetch', + data, + level: 'error', + type: 'http', + }, + hint, + ); + } else { + const response = handlerData.response as Response | undefined; + const data: FetchBreadcrumbData = { + ...handlerData.fetchData, + status_code: response && response.status, + }; + const hint: FetchBreadcrumbHint = { + input: handlerData.args, + response, + startTimestamp, + endTimestamp, + }; + addBreadcrumb( + { + category: 'fetch', + data, + type: 'http', + }, + hint, + ); + } + }; +} diff --git a/packages/deno/src/sdk.ts b/packages/deno/src/sdk.ts index b0452ca5302e..9ce7761bcea9 100644 --- a/packages/deno/src/sdk.ts +++ b/packages/deno/src/sdk.ts @@ -1,11 +1,16 @@ -import { breadcrumbsIntegration, dedupeIntegration } from '@sentry/browser'; import type { ServerRuntimeClientOptions } from '@sentry/core'; -import { functionToStringIntegration, inboundFiltersIntegration, linkedErrorsIntegration } from '@sentry/core'; +import { + dedupeIntegration, + functionToStringIntegration, + inboundFiltersIntegration, + linkedErrorsIntegration, +} from '@sentry/core'; import { getIntegrationsToSetup, initAndBind } from '@sentry/core'; import type { Integration, Options, StackParser } from '@sentry/types'; import { createStackParser, nodeStackLineParser, stackParserFromStackParserOptions } from '@sentry/utils'; import { DenoClient } from './client'; +import { breadcrumbsIntegration } from './integrations/breadcrumbs'; import { denoContextIntegration } from './integrations/context'; import { contextLinesIntegration } from './integrations/contextlines'; import { globalHandlersIntegration } from './integrations/globalhandlers'; @@ -21,14 +26,9 @@ export function getDefaultIntegrations(_options: Options): Integration[] { inboundFiltersIntegration(), functionToStringIntegration(), linkedErrorsIntegration(), - // From Browser dedupeIntegration(), - breadcrumbsIntegration({ - dom: false, - history: false, - xhr: false, - }), // Deno Specific + breadcrumbsIntegration(), denoContextIntegration(), contextLinesIntegration(), normalizePathsIntegration(), From 2f058b2983724213410536666e8e8d466ed0d4bc Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 1 Mar 2024 09:59:11 +0000 Subject: [PATCH 15/17] meta(gitflow): Merge changelog from master into develop (#10883) To avoid conflicts we have right now here: https://github.com/getsentry/sentry-javascript/pull/10881 --- CHANGELOG.md | 258 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 258 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e0d99d6e4ded..237a8f79099f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,264 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 8.0.0-alpha.1 + +This is the first Alpha release of the v8 cycle, which includes a variety of breaking changes. + +Read the [in-depth migration guide](./MIGRATION.md) to find out how to address any breaking changes in your code. + +### Important Changes + +**- feat(node): Make `@sentry/node` powered by OpenTelemetry (#10762)** + +The biggest change is the switch to use OpenTelemetry under the hood in `@sentry/node`. This brings with it a variety of +changes: + +- There is now automated performance instrumentation for Express, Fastify, Nest.js and Koa. You can remove any + performance and request isolation code you previously wrote manually for these frameworks. +- All performance instrumenttion is enabled by default, and will only take effect if the instrumented package is used. + You don't need to use `autoDiscoverNodePerformanceMonitoringIntegrations()` anymore. +- You need to ensure to call `Sentry.init()` _before_ you import any other packages. Otherwise, the packages cannot be + instrumented: + +```js +const Sentry = require('@sentry/node'); +Sentry.init({ + dsn: '...', + // ... other config here +}); +// now require other things below this! +const http = require('http'); +const express = require('express'); +// .... +``` + +- Currently, we only support CJS-based Node application out of the box. There is experimental ESM support, see + [the instructions](./packages/node-experimental/README.md#esm-support). +- `startTransaction` and `span.startChild()` are no longer supported. This is due to the underlying change to + OpenTelemetry powered performance instrumentation. See + [docs on the new performance APIs](./docs/v8-new-performance-apis.md) for details. + +Related changes: + +- feat(node-experimental): Add missing re-exports (#10679) +- feat(node-experimental): Move `defaultStackParser` & `getSentryRelease` (#10722) +- feat(node-experimental): Move `errorHandler` (#10728) +- feat(node-experimental): Move cron code over (#10742) +- feat(node-experimental): Move integrations from node (#10743) +- feat(node-experimental): Properly set request & session on http requests (#10676) +- feat(opentelemetry): Support `forceTransaction` in OTEL (#10807) +- feat(opentelemetry): Align span options with core span options (#10761) +- feat(opentelemetry): Do not capture span events as breadcrumbs (#10612) +- feat(opentelemetry): Ensure DSC & attributes are correctly set (#10806) +- feat(opentelemetry): Fix & align isolation scope usage in node-experimental (#10570) +- feat(opentelemetry): Merge node-experimental changes into opentelemetry (#10689) +- ref(node-experimental): Cleanup re-exports (#10741) +- ref(node-experimental): Cleanup tracing intergations (#10730) +- ref(node-experimental): Copy transport & client to node-experimental (#10720) +- ref(node-experimental): Remove custom `isInitialized` (#10607) +- ref(node-experimental): Remove custom hub & scope (#10616) +- ref(node-experimental): Remove deprecated class integrations (#10675) +- ref(node-experimental): Rename `errorHandler` to `expressErrorHandler` (#10746) +- ref(node-integration-tests): Migrate to new Http integration (#10765) +- ref(node): Align semantic attribute handling (#10827) + +**- feat: Remove `@sentry/integrations` package (#10799)** + +This package is no longer published. You can instead import these pluggable integrations directly from your SDK package +(e.g. `@sentry/browser` or `@sentry/react`). + +**- feat: Remove `@sentry/hub` package (#10783)** + +This package is no longer published. You can instead import directly from your SDK package (e.g. `@sentry/react` or +`@sentry/node`). + +**- feat(v8): Remove @sentry/tracing (#10625)** + +This package is no longer published. You can instead import directly from your SDK package (e.g. `@sentry/react` or +`@sentry/node`). + +**- feat: Set required node version to >=14.8.0 for all packages (#10834)** + +The minimum required node version is now 14.8+. If you need support for older node versions, you can stay on the v7 +branch. + +**- Removed class-based integrations** + +We have removed most of the deprecated class-based integrations. Instead, you can use the functional styles: + +```js +import * as Sentry from '@sentry/browser'; +// v7 +Sentry.init({ + integrations: [new Sentry.BrowserTracing()], +}); +// v8 +Sentry.init({ + integrations: [new Sentry.browserTracingIntegration()], +}); +``` + +- ref: Remove `BrowserTracing` (#10653) +- feat(v8/node): Remove LocalVariables class integration (#10558) +- feat(v8/react): Delete react router exports (#10532) +- feat(v8/vue): Remove all deprecated exports from vue (#10533) +- feat(v8/wasm): Remove deprecated exports (#10552) + +**- feat(v8/browser): Remove XHR transport (#10703)** + +We have removed the XHR transport, and are instead using the fetch-based transport now by default. This means that if +you are using Sentry in a browser environment without fetch, you'll need to either provide a fetch polyfill, or provide +a custom transport to Sentry. + +**- feat(sveltekit): Update `@sentry/vite-plugin` to 2.x and adjust options API (#10813)** + +We have updated `@sentry/sveltekit` to use the latest version of `@sentry/vite-plugin`, which lead to changes in +configuration options. + +### Other Changes + +- feat: Allow passing `null` to `withActiveSpan` (#10717) +- feat: Implement new Async Context Strategy (#10647) +- feat: Remove `hub` from global, `hub.run` & hub utilities (#10718) +- feat: Update default trace propagation targets logic in the browser (#10621) +- feat(browser): Export `getIsolationScope` and `getGlobalScope` (#10658) +- feat(core): Add metric summaries to spans (#10554) +- feat(core): Decouple metrics aggregation from client (#10628) +- feat(core): Lookup client on current scope, not hub (#10635) +- feat(core): Make `setXXX` methods set on isolation scope (#10678) +- feat(core): Make custom tracing methods return spans & set default op (#10633) +- feat(core): Make global `addBreadcrumb` write to the isolation scope instead of current scope (#10586) +- feat(core): Remove health check transaction filters (#10818) +- feat(core): Streamline custom hub creation for node-experimental (#10555) +- feat(core): Update `addEventProcessor` to add to isolation scope (#10606) +- feat(core): Update `Sentry.addBreadcrumb` to skip hub (#10601) +- feat(core): Use global `TextEncoder` and `TextDecoder` (#10701) +- feat(deps): bump @sentry/cli from 2.26.0 to 2.28.0 (#10496) +- feat(deps): bump @sentry/cli from 2.28.0 to 2.28.5 (#10620) +- feat(deps): bump @sentry/cli from 2.28.5 to 2.28.6 (#10727) +- feat(integrations): Capture error arguments as exception regardless of level in `captureConsoleIntegration` (#10744) +- feat(metrics): Remove metrics method from `BaseClient` (#10789) +- feat(node): Remove unnecessary URL imports (#10860) +- feat(react): Drop support for React 15 (#10115) +- feat(remix): Add Vite dev-mode support to Express instrumentation. (#10784) +- fix: Export session API (#10711) +- fix(angular-ivy): Add `exports` field to `package.json` (#10569) +- fix(angular): Ensure navigations always create a transaction (#10646) +- fix(core): Add lost scope tests & fix update case (#10738) +- fix(core): Fix scope capturing via `captureContext` function (#10735) +- fix(feedback): Replay breadcrumb for feedback events was incorrect (#10536) +- fix(nextjs): Remove `webpack://` prefix more broadly from source map `sources` field (#10642) +- fix(node): import `worker_threads` and fix node v14 types (#10791) +- fix(node): Record local variables with falsy values, `null` and `undefined` (#10821) +- fix(stacktrace): Always use `?` for anonymous function name (#10732) +- fix(sveltekit): Avoid capturing Http 4xx errors on the client (#10571) +- fix(sveltekit): Ensure navigations and redirects always create a new transaction (#10656) +- fix(sveltekit): Properly await sourcemaps flattening (#10602) +- fix(types): Improve attachment type (#10832) +- fx(node): Fix anr worker check (#10719) +- ref: Cleanup browser profiling integration (#10766) +- ref: Collect child spans references via non-enumerable on Span object (#10715) +- ref: Make scope setters on hub only write to isolation scope (#10572) +- ref: Store runtime on isolation scope (#10657) +- ref(astro): Put request as SDK processing metadata instead of span data (#10840) +- ref(core): Always use a (default) ACS (#10644) +- ref(core): Make `on` and `emit` required on client (#10603) +- ref(core): Make remaining client methods required (#10605) +- ref(core): Rename `Span` class to `SentrySpan` (#10687) +- ref(core): Restructure hub exports (#10639) +- ref(core): Skip hub in top level `captureXXX` methods (#10688) +- ref(core): Allow `number` as span `traceFlag` (#10855) +- ref(core): Remove `status` field from Span (#10856) +- ref(remix): Make `@remix-run/router` a dependency. (#10479) +- ref(replay): Use `beforeAddBreadcrumb` hook instead of scope listener (#10600) +- ref(sveltekit): Hard-pin Vite plugin version (#10843) + +### Other Deprecation Removals/Changes + +We have also removed or updated a variety of deprecated APIs. + +- feat(v8): Remove `extractTraceparentData` export (#10559) +- feat(v8): Remove defaultIntegrations deprecated export (#10691) +- feat(v8): Remove deprecated `span.isSuccess` method (#10699) +- feat(v8): Remove deprecated `traceHeaders` method (#10776) +- feat(v8): Remove deprecated addInstrumentationHandler (#10693) +- feat(v8): Remove deprecated configureScope call (#10565) +- feat(v8): Remove deprecated runWithAsyncContext API (#10780) +- feat(v8): Remove deprecated spanStatusfromHttpCode export (#10563) +- feat(v8): Remove deprecated trace and startActiveSpan methods (#10593) +- feat(v8): Remove requestData deprecations (#10626) +- feat(v8): Remove Severity enum (#10551) +- feat(v8): Remove span.origin (#10753) +- feat(v8): Remove span.toTraceparent method (#10698) +- feat(v8): Remove usage of span.description and span.name (#10697) +- feat(v8): Update eventFromUnknownInput to only use client (#10692) +- feat(v8/astro): Remove deprecated exports from Astro SDK (#10611) +- feat(v8/browser): Remove `_eventFromIncompleteOnError` usage (#10553) +- feat(v8/browser): Remove XHR transport (#10703) +- feat(v8/browser): Rename TryCatch integration to `browserApiErrorsIntegration` (#10755) +- feat(v8/core): Remove deprecated setHttpStatus (#10774) +- feat(v8/core): Remove deprecated updateWithContext method (#10800) +- feat(v8/core): Remove getters for span.op (#10767) +- feat(v8/core): Remove span.finish call (#10773) +- feat(v8/core): Remove span.instrumenter and instrumenter option (#10769) +- feat(v8/ember): Remove deprecated exports (#10535) +- feat(v8/integrations): Remove deprecated exports (#10556) +- feat(v8/node): Remove deepReadDirSync export (#10564) +- feat(v8/node): Remove deprecated anr methods (#10562) +- feat(v8/node): Remove getModuleFromFilename export (#10754) +- ref: Make `setupOnce` optional in integrations (#10729) +- ref: Migrate transaction source from metadata to attributes (#10674) +- ref: Refactor remaining `makeMain` usage (#10713) +- ref(astro): Remove deprecated Replay and BrowserTracing (#10768) +- feat(core): Remove deprecated `scope.applyToEvent()` method (#10842) +- ref(integrations): Remove offline integration (#9456) +- ref(nextjs): Remove all deprecated API (#10549) +- ref: Remove `lastEventId` (#10585) +- ref: Remove `reuseExisting` option for ACS (#10645) +- ref: Remove `tracingOrigins` options (#10614) +- ref: Remove deprecated `showReportDialog` APIs (#10609) +- ref: Remove usage of span tags (#10808) +- ref: Remove user segment (#10575) + +## 7.103.0 + +### Important Changes + +- **feat(core): Allow to pass `forceTransaction` to `startSpan()` APIs (#10819)** + +You can now pass `forceTransaction: true` to `startSpan()`, `startSpanManual()` and `startInactiveSpan()`. This allows +you to start a span that you want to be a transaction, if possible. Under the hood, the SDK will connect this span to +the running active span (if there is one), but still send the new span as a transaction to the Sentry backend, if +possible, ensuring it shows up as a transaction throughout the system. + +Please note that setting this to `true` does not _guarantee_ that this will be sent as a transaction, but that the SDK +will try to do so. You can enable this flag if this span is important to you and you want to ensure that you can see it +in the Sentry UI. + +### Other Changes + +- fix: Make breadcrumbs option optional in WinterCGFetch integration (#10792) + +## 7.102.1 + +- fix(performance): Fixes latest route name and source for interactions not updating properly on navigation (#10702) +- fix(tracing): Guard against missing `window.location` (#10659) +- ref: Make span types more robust (#10660) +- ref(remix): Make `@remix-run/router` a dependency (v7) (#10779) + +## 7.102.0 + +- fix: Export session API (#10712) +- fix(core): Fix scope capturing via `captureContext` function (#10737) + +## 7.101.1 + +In version 7.101.0 the `@sentry/hub` package was missing due to a publishing issue. This release contains the package +again. + +- fix(nextjs): Remove `webpack://` prefix more broadly from source map `sources` field (#10641) + ## 7.101.0 - feat: Export semantic attribute keys from SDK packages (#10637) From af6f4d6cd658b2b37ebf319a086d4c2223cf5ff3 Mon Sep 17 00:00:00 2001 From: Luca Forstner Date: Fri, 1 Mar 2024 11:00:47 +0100 Subject: [PATCH 16/17] ci: Reduce large runner usage (#10873) --- .github/workflows/build.yml | 4 ++-- .github/workflows/flaky-test-detector.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f48cd5b3597a..f3d9ca91f25a 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -620,7 +620,7 @@ jobs: name: Playwright (${{ matrix.bundle }}${{ matrix.shard && format(' {0}/{1}', matrix.shard, matrix.shards) || ''}}) Tests needs: [job_get_metadata, job_build] if: needs.job_get_metadata.outputs.changed_browser_integration == 'true' || github.event_name != 'pull_request' - runs-on: ubuntu-20.04-large-js + runs-on: ubuntu-20.04 timeout-minutes: 25 strategy: fail-fast: false @@ -783,7 +783,7 @@ jobs: name: Browser (${{ matrix.browser }}) Tests needs: [job_get_metadata, job_build] if: needs.job_get_metadata.outputs.changed_browser == 'true' || github.event_name != 'pull_request' - runs-on: ubuntu-20.04-large-js + runs-on: ubuntu-20.04 timeout-minutes: 20 strategy: fail-fast: false diff --git a/.github/workflows/flaky-test-detector.yml b/.github/workflows/flaky-test-detector.yml index b123afdc141b..6c27000c6914 100644 --- a/.github/workflows/flaky-test-detector.yml +++ b/.github/workflows/flaky-test-detector.yml @@ -23,7 +23,7 @@ concurrency: jobs: flaky-detector: - runs-on: ubuntu-20.04-large-js + runs-on: ubuntu-20.04 timeout-minutes: 60 name: 'Check tests for flakiness' # Also skip if PR is from master -> develop From ea58011156435df10454afe2a01c08adda38b460 Mon Sep 17 00:00:00 2001 From: Francesco Novy Date: Fri, 1 Mar 2024 09:47:34 +0000 Subject: [PATCH 17/17] meta(changelog): Update changelog for v8.0.0-alpha.1 Co-authored-by: Lukas Stracke --- CHANGELOG.md | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 237a8f79099f..a047c2c9ea59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,7 +19,7 @@ changes: - There is now automated performance instrumentation for Express, Fastify, Nest.js and Koa. You can remove any performance and request isolation code you previously wrote manually for these frameworks. -- All performance instrumenttion is enabled by default, and will only take effect if the instrumented package is used. +- All performance instrumention is enabled by default, and will only take effect if the instrumented package is used. You don't need to use `autoDiscoverNodePerformanceMonitoringIntegrations()` anymore. - You need to ensure to call `Sentry.init()` _before_ you import any other packages. Otherwise, the packages cannot be instrumented: @@ -121,11 +121,14 @@ configuration options. ### Other Changes +- feat: Ensure `withActiveSpan` is exported everywhere (#10878) - feat: Allow passing `null` to `withActiveSpan` (#10717) - feat: Implement new Async Context Strategy (#10647) - feat: Remove `hub` from global, `hub.run` & hub utilities (#10718) - feat: Update default trace propagation targets logic in the browser (#10621) +- feat: Ignore ResizeObserver and undefined error (#10845) - feat(browser): Export `getIsolationScope` and `getGlobalScope` (#10658) +- feat(browser): Prevent initialization in browser extensions (#10844) - feat(core): Add metric summaries to spans (#10554) - feat(core): Decouple metrics aggregation from client (#10628) - feat(core): Lookup client on current scope, not hub (#10635) @@ -210,6 +213,8 @@ We have also removed or updated a variety of deprecated APIs. - feat(v8/node): Remove deepReadDirSync export (#10564) - feat(v8/node): Remove deprecated anr methods (#10562) - feat(v8/node): Remove getModuleFromFilename export (#10754) +- feat(core): Remove deprecated props from `Span` interface (#10854) +- fix(v8): Remove deprecated tracing config (#10870) - ref: Make `setupOnce` optional in integrations (#10729) - ref: Migrate transaction source from metadata to attributes (#10674) - ref: Refactor remaining `makeMain` usage (#10713) @@ -224,6 +229,53 @@ We have also removed or updated a variety of deprecated APIs. - ref: Remove usage of span tags (#10808) - ref: Remove user segment (#10575) +## 7.105.0 + +### Important Changes + +- **feat: Ensure `withActiveSpan` is exported everywhere (#10877)** + +You can use the `withActiveSpan` method to ensure a certain span is the active span in a given callback. This can be +used to create a span as a child of a specific span with the `startSpan` API methods: + +```js +const parentSpan = Sentry.startInactiveSpan({ name: 'parent' }); +if (parentSpan) { + withActiveSpan(parentSpan, () => { + // This will be a direct child of parentSpan + const childSpan = Sentry.startInactiveSpan({ name: 'child' }); + }); +} +``` + +## 7.104.0 + +### Important Changes + +- **feat(performance): create Interaction standalone spans on inp events (#10709)** + +This release adds support for the INP web vital. This is currently only supported for Saas Sentry, and product support +is released with the upcoming `24.3.0` release of self-hosted. + +To opt-in to this feature, you can use the `enableInp` option in the `browserTracingIntegration`: + +```js +Sentry.init({ + integrations: [ + Sentry.browserTracingIntegration({ + enableInp: true, + }); + ] +}) +``` + +### Other Changes + +- feat(feedback): Flush replays when feedback form opens (#10567) +- feat(profiling-node): Expose `nodeProfilingIntegration` (#10864) +- fix(profiling-node): Fix dependencies to point to current versions (#10861) +- fix(replay): Add `errorHandler` for replayCanvas integration (#10796) + ## 7.103.0 ### Important Changes