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 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 diff --git a/MIGRATION.md b/MIGRATION.md index 0ab76f759ab7..aae2f3ee4292 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,1730 +1,1212 @@ -# 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) -This will let you select which updates to run, and automatically update your code. Make sure to still review all code -changes! +### Performance Monitoring API -## Depreacted `BrowserTracing` integration +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. -The `BrowserTracing` integration, together with the custom routing instrumentations passed to it, are deprecated in v8. -Instead, you should use `Sentry.browserTracingIntegration()`. +Instead of using `startTransaction` and `span.startChild`, you rely on new helper methods to create spans top level +helpers to create spans. -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: +```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(); +}); -### `@sentry/browser`, `@sentry/svelte`, `@sentry/gatsby` +// `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(); +}); -```js -import * as Sentry from '@sentry/browser'; +// You can nest spans via more `startSpan` calls. +const value = Sentry.startSpan({ name: 'mySpan' }, span => { + span.setAttribute('key1', 'value1'); -Sentry.init({ - integrations: [Sentry.browserTracingIntegration()], + // `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(); + }); +} ``` -### `@sentry/react` +You can [read more about the new performance APIs here](./docs/v8-new-performance-apis.md). -```js -import * as Sentry from '@sentry/react'; +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()` -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, - }), - ], -}); -``` +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. -### `@sentry/vue` +See [Deprecate Hub](./MIGRATION.md#deprecate-hub) for details on how to replace existing usage of the Hub APIs. + +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/vue'; +// Before (v7) +const replay = Sentry.getIntegration(Replay); -Sentry.init({ - integrations: [ - Sentry.browserTracingIntegration({ - // pass router in, if applicable - router, - }), - ], -}); +// After (v8) +const replay = getClient().getIntegrationByName('Replay'); ``` -### `@sentry/angular` & `@sentry/angular-ivy` +#### `tracingOrigins` has been replaced by `tracePropagationTargets` -```js -import * as Sentry from '@sentry/angular'; +`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({ - integrations: [Sentry.browserTracingIntegration()], + dsn: '__DSN__', + integrations: [new Sentry.BrowserTracing({ tracingOrigins: ['localhost', 'example.com'] })], }); -// You still need to add the Trace Service like before! +// After (v8) +Sentry.init({ + dsn: '__DSN__', + integrations: [Sentry.browserTracingIntegration()], + tracePropagationTargets: ['localhost', 'example.com'], +}); ``` -### `@sentry/remix` +#### Removal of the `MetricsAggregator` integration class and `metricsAggregatorIntegration` -```js -import * as Sentry from '@sentry/remix'; +The SDKs now support metrics features without any additional configuration. +```ts +// Before (v7) +// Server (Node/Deno/Bun) Sentry.init({ - integrations: [ - Sentry.browserTracingIntegration({ - useEffect, - useLocation, - useMatches, - }), - ], + dsn: '__DSN__', + _experiments: { + metricsAggregator: true, + }, }); -``` - -### `@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'; +// Before (v7) +// Browser +Sentry.init({ + dsn: '__DSN__', + integrations: [Sentry.metricsAggregatorIntegration()], +}); +// After (v8) Sentry.init({ - integrations: [ - Sentry.browserTracingIntegration({ - // add custom options here - }), - ], + dsn: '__DSN__', }); ``` -### `@sentry/ember` +#### Removal of Severity Enum -Browser tracing is automatically set up for you. You can configure it as before through configuration. +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. -## Deprecated `transactionContext` passed to `tracesSampler` +```js +// Before (v7) +import { Severity, SeverityLevel } from '@sentry/types'; -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). +const levelA = Severity.error; -## Deprecate using `getClient()` to check if the SDK was initialized +const levelB: SeverityLevel = "error" -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. +// After (v8) +import { SeverityLevel } from '@sentry/types'; -## Deprecate `getCurrentHub()` +const levelA = "error" as SeverityLevel; -In v8, you will no longer have a Hub, only Scopes as a concept. This also means that `getCurrentHub()` will eventually -be removed. +const levelB: SeverityLevel = "error" +``` -Instead of `getCurrentHub()`, use the respective replacement API directly - see [Deprecate Hub](#deprecate-hub) for -details. +#### Removal of `Sentry.configureScope` method -## Deprecate class-based integrations +The top level `Sentry.configureScope` function has been removed. Instead, you should use the `Sentry.getCurrentScope()` +to access and mutate the current scope. -In v7, integrations are classes and can be added as e.g. `integrations: [new Sentry.Integrations.ContextLines()]`. 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 +```js +// Before (v7) +Sentry.configureScope(scope => { + scope.setTag('key', 'value'); +}); -- for example, `new Integrations.LinkedErrors()` becomes `linkedErrorsIntegration()`. +// After (v8) +Sentry.getCurrentScope().setTag('key', 'value'); +``` -The following list shows how integrations should be migrated: +#### Removal of `Span` class export from SDK packages -| 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` | +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. -## Deprecate `hub.bindClient()` and `makeMain()` +#### Removal of `spanStatusfromHttpCode` in favour of `getSpanStatusFromHttpCode` -Instead, either directly use `initAndBind()`, or the new APIs `setCurrentClient()` and `client.init()`. See -[Initializing the SDK in v8](./docs/v8-initializing.md) for more details. - -## Deprecate `Transaction` integration - -This pluggable integration from `@sentry/integrations` will be removed in v8. It was already undocumented and is not -necessary for the SDK to work as expected. - -## Changed integration interface - -In v8, integrations passed to a client will have an optional `setupOnce()` hook. Currently, this hook is always present, -but in v8 you will not be able to rely on this always existing anymore - any integration _may_ have a `setup` and/or a -`setupOnce` hook. Additionally, `setupOnce()` will not receive any arguments anymore. - -This should not affect most people, but in the case that you are manually calling `integration.setupOnce()` right now, -make sure to guard it's existence properly. - -## Deprecate `getIntegration()` and `getIntegrationById()` - -This deprecates `getIntegration()` on both the hub & the client, as well as `getIntegrationById()` on the baseclient. -Instead, use `getIntegrationByName()`. You can optionally pass an integration generic to make it easier to work with -typescript: - -```ts -const replay = getClient().getIntegrationByName('Replay'); -``` - -## Deprecate `Hub` - -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". - -`Scope`s have existed before in the SDK but we are now expanding on them because we have found them powerful enough to -fully cover the `Hub` API. - -If you are using the `Hub` right now, see the following table on how to migrate to the new API: - -| Old `Hub` API | New `Scope` API | -| ---------------------- | ------------------------------------------------------------------------------------ | -| `new Hub()` | `withScope()`, `withIsolationScope()` or `new Scope()` | -| hub.isOlderThan() | REMOVED - Was used to compare `Hub` instances, which are gonna be removed | -| hub.bindClient() | A combination of `scope.setClient()` and `client.init()` | -| hub.pushScope() | `Sentry.withScope()` | -| hub.popScope() | `Sentry.withScope()` | -| hub.withScope() | `Sentry.withScope()` | -| getClient() | `Sentry.getClient()` | -| getScope() | `Sentry.getCurrentScope()` to get the currently active scope | -| getIsolationScope() | `Sentry.getIsolationScope()` | -| getStack() | REMOVED - The stack used to hold scopes. Scopes are used directly now | -| getStackTop() | REMOVED - The stack used to hold scopes. Scopes are used directly now | -| captureException() | `Sentry.captureException()` | -| captureMessage() | `Sentry.captureMessage()` | -| captureEvent() | `Sentry.captureEvent()` | -| lastEventId() | REMOVED - Use event processors or beforeSend instead | -| addBreadcrumb() | `Sentry.addBreadcrumb()` | -| setUser() | `Sentry.setUser()` | -| setTags() | `Sentry.setTags()` | -| setExtras() | `Sentry.setExtras()` | -| setTag() | `Sentry.setTag()` | -| setExtra() | `Sentry.setExtra()` | -| setContext() | `Sentry.setContext()` | -| configureScope() | REMOVED - Scopes are now the unit of concurrency | -| run() | `Sentry.withScope()` or `Sentry.withIsolationScope()` | -| getIntegration() | `client.getIntegration()` | -| startTransaction() | `Sentry.startSpan()`, `Sentry.startInactiveSpan()` or `Sentry.startSpanManual()` | -| traceHeaders() | REMOVED - The closest equivalent is now `spanToTraceHeader(getActiveSpan())` | -| captureSession() | `Sentry.captureSession()` | -| startSession() | `Sentry.startSession()` | -| endSession() | `Sentry.endSession()` | -| shouldSendDefaultPii() | REMOVED - The closest equivalent is `Sentry.getClient().getOptions().sendDefaultPii` | - -The `Hub` constructor is also deprecated and will be removed in the next major version. If you are creating Hubs for -multi-client use like so: - -```ts -// OLD -const hub = new Hub(); -hub.bindClient(client); -makeMain(hub); -``` - -instead initialize the client as follows: - -```ts -// NEW -Sentry.withIsolationScope(() => { - Sentry.setCurrentClient(client); - client.init(); -}); -``` - -If you are using the Hub to capture events like so: - -```ts -// OLD -const client = new Client(); -const hub = new Hub(client); -hub.captureException(); -``` - -instead capture isolated events as follows: - -```ts -// NEW -const client = new Client(); -const scope = new Scope(); -scope.setClient(client); -scope.captureException(); -``` - -## Deprecate `client.setupIntegrations()` - -Instead, use the new `client.init()` method. You should probably not use this directly and instead use `Sentry.init()`, -which calls this under the hood. But if you have a special use case that requires that, you can call `client.init()` -instead now. - -## Deprecate `scope.getSpan()` and `scope.setSpan()` - -Instead, you can get the currently active span via `Sentry.getActiveSpan()`. Setting a span on the scope happens -automatically when you use the new performance APIs `startSpan()` and `startSpanManual()`. - -## Deprecate `scope.setTransactionName()` - -Instead, either set this as attributes or tags, or use an event processor to set `event.transaction`. - -## Deprecate `scope.getTransaction()` and `getActiveTransaction()` - -Instead, you should not rely on the active transaction, but just use `startSpan()` APIs, which handle this for you. - -## Deprecate arguments for `startSpan()` APIs - -In v8, the API to start a new span will be reduced from the currently available options. Going forward, only these -argument will be passable to `startSpan()`, `startSpanManual()` and `startInactiveSpan()`: - -- `name` -- `attributes` -- `origin` -- `op` -- `startTime` -- `scope` - -## Deprecate `startTransaction()` & `span.startChild()` - -In v8, the old performance API `startTransaction()` (and `hub.startTransaction()`), as well as `span.startChild()`, will -be removed. Instead, use the new performance APIs: - -- `startSpan()` -- `startSpanManual()` -- `startInactiveSpan()` - -You can [read more about the new performance APIs here](./docs/v8-new-performance-apis.md). - -## Deprecate variations of `Sentry.continueTrace()` - -The version of `Sentry.continueTrace()` which does not take a callback argument will be removed in favor of the version -that does. Additionally, the callback argument will not receive an argument with the next major version. +In v8, we are removing the `spanStatusfromHttpCode` function in favor of `getSpanStatusFromHttpCode`. -Use `Sentry.continueTrace()` as follows: +```js +// Before (v7) +const spanStatus = spanStatusfromHttpCode(200); -```ts -app.get('/your-route', req => { - Sentry.withIsolationScope(isolationScope => { - Sentry.continueTrace( - { - sentryTrace: req.headers.get('sentry-trace'), - baggage: req.headers.get('baggage'), - }, - () => { - // All events recorded in this callback will be associated with the incoming trace. For example: - Sentry.startSpan({ name: '/my-route' }, async () => { - await doExpensiveWork(); - }); - }, - ); - }); -}); +// After (v8) +const spanStatus = getSpanStatusFromHttpCode(200); ``` -## Deprecate `Sentry.lastEventId()` and `hub.lastEventId()` +#### Removal of `addGlobalEventProcessor` in favour of `addEventProcessor` -`Sentry.lastEventId()` sometimes causes race conditions, so we are deprecating it in favour of the `beforeSend` -callback. +In v8, we are removing the `addGlobalEventProcessor` function in favor of `addEventProcessor`. ```js -// Before - -Sentry.init({ - beforeSend(event, hint) { - const lastCapturedEventId = Sentry.lastEventId(); - - // Do something with `lastCapturedEventId` here - - return event; - }, +// Before (v7) +addGlobalEventProcessor(event => { + delete event.extra; + return event; }); -// After -Sentry.init({ - beforeSend(event, hint) { - const lastCapturedEventId = event.event_id; - - // Do something with `lastCapturedEventId` here - - return event; - }, +// After (v8) +addEventProcessor(event => { + delete event.extra; + return event; }); ``` -## Deprecated fields on `Span` and `Transaction` - -In v8, the Span class is heavily reworked. The following properties & methods are thus deprecated: - -- `span.toContext()`: Access the fields directly instead. -- `span.updateWithContext(newSpanContext)`: Update the fields directly instead. -- `span.setName(newName)`: Use `span.updateName(newName)` instead. -- `span.toTraceparent()`: use `spanToTraceHeader(span)` util instead. -- `span.getTraceContext()`: Use `spanToTraceContext(span)` utility function instead. -- `span.sampled`: Use `span.isRecording()` instead. -- `span.spanId`: Use `span.spanContext().spanId` instead. -- `span.parentSpanId`: Use `spanToJSON(span).parent_span_id` instead. -- `span.traceId`: Use `span.spanContext().traceId` instead. -- `span.name`: Use `spanToJSON(span).description` instead. -- `span.description`: Use `spanToJSON(span).description` instead. -- `span.getDynamicSamplingContext`: Use `getDynamicSamplingContextFromSpan` utility function instead. -- `span.tags`: Set tags on the surrounding scope instead, or use attributes. -- `span.data`: Use `spanToJSON(span).data` instead. -- `span.setTag()`: Use `span.setAttribute()` instead or set tags on the surrounding scope. -- `span.setData()`: Use `span.setAttribute()` instead. -- `span.instrumenter` This field was removed and will be replaced internally. -- `span.transaction`: Use `getRootSpan` utility function instead. -- `span.spanRecorder`: Span recording will be handled internally by the SDK. -- `span.status`: Use `.setStatus` to set or update and `spanToJSON()` to read the span status. -- `span.op`: Use `startSpan` functions to set, `setAttribute()` to update and `spanToJSON` to read the span operation. -- `span.isSuccess`: Use `spanToJSON(span).status === 'ok'` instead. -- `transaction.setMetadata()`: Use attributes instead, or set data on the scope. -- `transaction.metadata`: Use attributes instead, or set data on the scope. -- `transaction.setContext()`: Set context on the surrounding scope instead. -- `transaction.setMeasurement()`: Use `Sentry.setMeasurement()` instead. In v8, setting measurements will be limited to - the currently active root span. -- `transaction.setName()`: Set the name with `.updateName()` and the source with `.setAttribute()` instead. -- `span.startTimestamp`: use `spanToJSON(span).start_timestamp` instead. You cannot update this anymore in v8. -- `span.endTimestamp`: use `spanToJSON(span).timestamp` instead. You cannot update this anymore in v8. You can pass a - custom end timestamp to `span.end(endTimestamp)`. - -## Deprecate `pushScope` & `popScope` in favor of `withScope` +#### Removal of `lastEventId()` method -Instead of manually pushing/popping a scope, you should use `Sentry.withScope(callback: (scope: Scope))` instead. +The `lastEventId` function has been removed. See [below](./MIGRATION.md#deprecate-lasteventid) for more details. -## Deprecate `configureScope` in favor of using `getCurrentScope()` +### Browser SDK (Browser, React, Vue, Angular, Ember, etc.) -Instead of updating the scope in a callback via `configureScope()`, you should access it via `getCurrentScope()` and -configure it directly: +Removed top-level exports: `Offline`, `makeXHRTransport`, `BrowserTracing` -```js -Sentry.getCurrentScope().setTag('xx', 'yy'); -``` +- [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) -## Deprecate `addGlobalEventProcessor` in favor of `addEventProcessor` +#### Removal of the `BrowserTracing` integration -Instead of using `addGlobalEventProcessor`, you should use `addEventProcessor` which does not add the event processor -globally, but to the current client. - -For the vast majority of cases, the behavior of these should be the same. Only in the case where you have multiple -clients will this differ - but you'll likely want to add event processors per-client then anyhow, not globally. - -In v8, we will remove the global event processors overall, as that allows us to avoid keeping global state that is not -necessary. - -## Deprecate `extractTraceParentData` export from `@sentry/core` & downstream packages - -Instead, import this directly from `@sentry/utils`. - -Generally, in most cases you should probably use `continueTrace` instead, which abstracts this away from you and handles -scope propagation for you. - -## Deprecate `lastEventId()` - -Instead, if you need the ID of a recently captured event, we recommend using `beforeSend` instead: - -```ts -import * as Sentry from '@sentry/browser'; +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) -Sentry.init({ - dsn: '__DSN__', - beforeSend(event, hint) { - const lastCapturedEventId = event.event_id; +#### Removal of the `Offline` integration - // Do something with `lastCapturedEventId` here +The `Offline` integration has been removed in favor of the +[offline transport wrapper](http://docs.sentry.io/platforms/javascript/configuration/transports/#offline-caching). - return event; - }, -}); -``` +#### Removal of `makeXHRTransport` transport -## Deprecate `timestampWithMs` export - #7878 - -The `timestampWithMs` util is deprecated in favor of using `timestampInSeconds`. +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. -## `addTracingExtensions` replaces `addExtensionMethods` (since 7.46.0) +### Server-side SDKs (Node, Deno, Bun, etc.) -Since the deprecation of `@sentry/tracing`, tracing extensions are now added by calling `addTracingExtensions` which is -exported from all framework SDKs. +Removed top-level exports: `enableAnrDetection`, `Anr`, `deepReadDirSync` -```js -// Before -import * as Sentry from '@sentry/browser'; -import { addExtensionMethods } from '@sentry/tracing'; +- [Removal of `enableAnrDetection` and `Anr` class](./MIGRATION.md#removal-of-enableanrdetection-and-anr-class) +- [Removal of `deepReadDirSync` method](./MIGRATION.md#removal-of-deepreaddirsync-method) -Sentry.init({ - dsn: '__DSN__', - tracesSampleRate: 1.0, -}); +#### Removal of `enableAnrDetection` and `Anr` class -addExtensionMethods(); +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: -// After -import * as Sentry from '@sentry/browser'; +#### Removal of `deepReadDirSync` method -Sentry.init({ - dsn: '__DSN__', - tracesSampleRate: 1.0, -}); +The `deepReadDirSync` method has been removed. There is no replacement API. -Sentry.addTracingExtensions(); -``` +### Next.js SDK -## Remove requirement for `@sentry/tracing` package (since 7.46.0) +Removed top-level exports: `withSentryApi`, `withSentryAPI`, `withSentryGetServerSideProps`, `withSentryGetStaticProps`, +`withSentryServerSideGetInitialProps`, `withSentryServerSideAppGetInitialProps`, +`withSentryServerSideDocumentGetInitialProps`, `withSentryServerSideErrorGetInitialProps`, `nextRouterInstrumentation`, +`IS_BUILD`, `isBuild` -With `7.46.0` you no longer require the `@sentry/tracing` package to use tracing and performance monitoring with the -Sentry JavaScript SDKs. The `@sentry/tracing` package will be removed in a future major release, but can still be used -in the meantime. +- [Removal of deprecated API in `@sentry/nextjs`](./MIGRATION.md#removal-of-deprecated-api-in-sentrynextjs) -#### Browser: +#### Removal of deprecated API in `@sentry/nextjs` -```js -// Before -import * as Sentry from '@sentry/browser'; -import { BrowserTracing } from '@sentry/tracing'; +The following previously deprecated API has been removed from the `@sentry/nextjs` package: -Sentry.init({ - dsn: '__DSN__', - tracesSampleRate: 1.0, - integrations: [new BrowserTracing()], -}); +- `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` -// After -import * as Sentry from '@sentry/browser'; +### Astro SDK -Sentry.init({ - dsn: '__DSN__', - tracesSampleRate: 1.0, - integrations: [new Sentry.BrowserTracing()], -}); -``` +#### Removal of `trackHeaders` option for Astro middleware -#### Node: +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 -// Before -const Sentry = require('@sentry/node'); -require('@sentry/tracing'); - -Sentry.init({ - dsn: '__DSN__', - tracesSampleRate: 1.0, -}); - -// After -const Sentry = require('@sentry/node'); - Sentry.init({ - dsn: '__DSN__', - tracesSampleRate: 1.0, integrations: [ - // Automatically instrument Node.js libraries and frameworks - ...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations(), + Sentry.requestDataIntegration({ + include: { + headers: false, + }, + }), ], }); ``` -**Note:** If you imported `stripUrlQueryAndFragment` from `@sentry/tracing`, you'll need to import it from -`@sentry/utils`, once you remove `@sentry/tracing`. - -## Replay options changed (since 7.35.0) - #6645 +## 5. Behaviour Changes -Some options for replay have been deprecated in favor of new APIs. See -[Replay Migration docs](./packages/replay/MIGRATION.md#upgrading-replay-from-7340-to-7350) for details. +- [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) -## Renaming of Next.js wrapper methods (since 7.31.0) - #6790 +#### Updated behaviour of `tracePropagationTargets` in the browser (HTTP tracing headers & CORS) -We updated the names of the functions to wrap data fetchers and API routes to better reflect what they are doing. The -old methods can still be used but are deprecated and will be removed in the next major update of the SDK. - -Following function names were updated: - -- `withSentryAPI` was renamed to `wrapApiHandlerWithSentry` -- `withSentryGetServerSideProps` was renamed to `wrapGetServerSidePropsWithSentry` -- `withSentryGetStaticProps` was renamed to `wrapGetStaticPropsWithSentry` -- `withSentryServerSideGetInitialProps` was renamed to `wrapGetInitialPropsWithSentry` -- `withSentryServerSideAppGetInitialProps` was renamed to `wrapAppGetInitialPropsWithSentry` -- `withSentryServerSideDocumentGetInitialProps` was renamed to `wrapDocumentGetInitialPropsWithSentry` -- `withSentryServerSideErrorGetInitialProps` was renamed to `wrapErrorGetInitialPropsWithSentry` - -## Deprecated `tracingOrigins` (since 7.19.0) - #6176 - -The `tracingOrigins` option is deprecated in favor of using `shouldCreateSpanForRequest` and `tracePropagationTargets`. - -## Deprecate `componentTrackingPreprocessor` in Svelte SDK (since 7.16.0) - #5936 - -This release adds the `withSentryConfig` feature to the Svelte SDK. It replaces the now deprecated Svelte -`componentTrackingPreprocessor` which will be removed in the next major release. - -## Deprecate `getGlobalObject` in `@sentry/utils` (since 7.16.0) - #5949 - -This is no longer used. - -## Deprecate @sentry/hub (since 7.15.0) - #5823 - -This release deprecates `@sentry/hub` and all of it's exports. All of the `@sentry/hub` exports have moved to -`@sentry/core`. `@sentry/hub` will be removed in the next major release. - -# Upgrading Sentry Replay (beta, 7.24.0) - -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 +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. -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. +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. -The following example shows how to create a custom transport in v7 vs. how it was done in v6: +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`. -```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'), - }, - })); - } +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"`. - // `createTransport` takes care of rate limiting and flushing - return createTransport(options, makeRequest); -} +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')`. -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 {...} - // ... -} +#### `extraErrorDataIntegration` changes -Sentry.init({ - dsn: '...', - transport: MyCustomTransport, // the constructor was called when the client was initialized - ... -}) -``` +The `extraErrorDataIntegration` integration now looks at +[`error.cause`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/cause) by +default. -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. +#### `transactionContext` no longer passed to `tracesSampler` -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). +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). -### Node Transport Changes +#### `getClient()` always returns a client -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`. +`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. -```ts -// New in v7: -Sentry.init({ - dsn: '...', - transportOptions: { - caCerts: getMyCaCert(), - proxy: 'http://example.com', - }, -}); +#### Removal of Client-Side health check transaction filters -// Before: -Sentry.init({ - dsn: '...', - caCerts: getMyCaCert(), - httpsProxy: 'http://example.com', -}); -``` +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. -## Enum Changes +# Deprecations in 7.x -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. +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+. -### Removed Enums +```bash +npx @sentry/migr8@latest +``` -- 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. +This will let you select which updates to run, and automatically update your code. Make sure to still review all code +changes! -### Deprecated Enums +## Deprecated `BrowserTracing` integration -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). +The `BrowserTracing` integration, together with the custom routing instrumentations passed to it, are deprecated in v8. +Instead, you should use `Sentry.browserTracingIntegration()`. -## Session Changes +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: -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: +### `@sentry/browser`, `@sentry/svelte`, `@sentry/gatsby` ```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'; +import * as Sentry from '@sentry/browser'; -const session = new Session({ release: 'v1.0' }); -session.update({ environment: 'prod' }); -session.close('ok'); +Sentry.init({ + integrations: [Sentry.browserTracingIntegration()], +}); ``` -## 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. +### `@sentry/react` ```js -// New in 6.18.0 -init({ - dsn: '__DSN__', - integrations: [new ContextLines({ frameContextLines: 10 })], -}); +import * as Sentry from '@sentry/react'; -// Before: -init({ - dsn: '__DSN__', - frameContextLines: 10, +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, + }), + ], }); ``` -# Upgrading from 6.x to 6.17.x +### `@sentry/vue` + +```js +import * as Sentry from '@sentry/vue'; -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. +Sentry.init({ + integrations: [ + Sentry.browserTracingIntegration({ + // pass router in, if applicable + router, + }), + ], +}); +``` -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. +### `@sentry/angular` & `@sentry/angular-ivy` ```js -// New in 6.17.0: -import { dsnToString, makeDsn } from '@sentry/utils'; - -const dsn = makeDsn(process.env.SENTRY_DSN); -console.log(dsnToString(dsn)); +import * as Sentry from '@sentry/angular'; -// Before: -import { Dsn } from '@sentry/utils'; +Sentry.init({ + integrations: [Sentry.browserTracingIntegration()], +}); -const dsn = new Dsn(process.env.SENTRY_DSN); -console.log(dsn.toString()); +// You still need to add the TraceService like before! ``` -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. +### `@sentry/remix` ```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(); +import * as Sentry from '@sentry/remix'; + +Sentry.init({ + integrations: [ + Sentry.browserTracingIntegration({ + useEffect, + useLocation, + useMatches, + }), + ], +}); ``` -## Enum changes +### `@sentry/nextjs`, `@sentry/astro`, `@sentry/sveltekit` -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. +Browser tracing is automatically set up for you in these packages. If you need to customize the options, you can do it +like this: -- `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 +```js +import * as Sentry from '@sentry/nextjs'; -#### Status +Sentry.init({ + integrations: [ + Sentry.browserTracingIntegration({ + // add custom options here + }), + ], +}); +``` -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. +### `@sentry/ember` -```js -// New in 6.17.0: -import { eventStatusFromHttpCode } from '@sentry/utils'; +Browser tracing is automatically set up for you. You can configure it as before through configuration. -const status = eventStatusFromHttpCode(500); +## Deprecated `transactionContext` passed to `tracesSampler` -// Before: -import { Status } from '@sentry/types'; +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). -const status = Status.fromHttpCode(500); -``` +## Deprecate using `getClient()` to check if the SDK was initialized -#### SpanStatus +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. -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. +## Deprecate `getCurrentHub()` -```js -// New in 6.17.0: -import { spanStatusfromHttpCode } from '@sentry/tracing'; +In v8, you will no longer have a Hub, only Scopes as a concept. This also means that `getCurrentHub()` will eventually +be removed. -const status = spanStatusfromHttpCode(403); +Instead of `getCurrentHub()`, use the respective replacement API directly - see [Deprecate Hub](#deprecate-hub) for +details. -// Before: -import { SpanStatus } from '@sentry/tracing'; +## Deprecate class-based integrations -const status = SpanStatus.fromHttpCode(403); -``` +In v7, integrations are classes and can be added as e.g. `integrations: [new Sentry.Integrations.ContextLines()]`. 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 -#### Severity, SeverityLevel, and SeverityLevels +- for example, `new Integrations.LinkedErrors()` becomes `linkedErrorsIntegration()`. -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. +The following list shows how integrations should be migrated: -```js -// New in 6.17.5: -import { SeverityLevel } from '@sentry/types'; +### 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` | +| `new ProfilingIntegration()` | `nodeProfilingIntegration()` | `@sentry/profiling-node` | +| `new BrowserProfilingIntegration()` | `browserProfilingIntegration()` | `@sentry/browser` | -const levelA = "error" as SeverityLevel; +## Deprecate `hub.bindClient()` and `makeMain()` -const levelB: SeverityLevel = "error" +Instead, either directly use `initAndBind()`, or the new APIs `setCurrentClient()` and `client.init()`. See +[Initializing the SDK in v8](./docs/v8-initializing.md) for more details. -// Before: -import { Severity, SeverityLevel } from '@sentry/types'; +## Deprecate `Transaction` integration -const levelA = Severity.error; +This pluggable integration from `@sentry/integrations` will be removed in v8. It was already undocumented and is not +necessary for the SDK to work as expected. -const levelB: SeverityLevel = "error" -``` +## Changed integration interface -# Upgrading from 4.x to 5.x/6.x +In v8, integrations passed to a client will have an optional `setupOnce()` hook. Currently, this hook is always present, +but in v8 you will not be able to rely on this always existing anymore - any integration _may_ have a `setup` and/or a +`setupOnce` hook. Additionally, `setupOnce()` will not receive any arguments anymore. -In this version upgrade, there are a few breaking changes. This guide should help you update your code accordingly. +This should not affect most people, but in the case that you are manually calling `integration.setupOnce()` right now, +make sure to guard it's existence properly. -## Integrations +## Deprecate `getIntegration()` and `getIntegrationById()` -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. +This deprecates `getIntegration()` on both the hub & the client, as well as `getIntegrationById()` on the baseclient. +Instead, use `getIntegrationByName()`. You can optionally pass an integration generic to make it easier to work with +typescript: -Integrations that are now opt-in and were default before: +```ts +const replay = getClient().getIntegrationByName('Replay'); +``` -- 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) +## Deprecate `Hub` -Integrations that were pluggable/optional before, that also live in this package: +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". -- Angular (browser) -- Debug (browser/node) -- Ember (browser) -- ReportingObserver (browser) -- RewriteFrames (browser/node) -- Transaction (browser/node) -- Vue (browser) +`Scope`s have existed before in the SDK but we are now expanding on them because we have found them powerful enough to +fully cover the `Hub` API. -### How to use `@sentry/integrations`? +If you are using the `Hub` right now, see the following table on how to migrate to the new API: -Lets start with the approach if you install `@sentry/browser` / `@sentry/node` with `npm` or `yarn`. +| Old `Hub` API | New `Scope` API | +| ---------------------- | ------------------------------------------------------------------------------------ | +| `new Hub()` | `withScope()`, `withIsolationScope()` or `new Scope()` | +| hub.isOlderThan() | REMOVED - Was used to compare `Hub` instances, which are gonna be removed | +| hub.bindClient() | A combination of `scope.setClient()` and `client.init()` | +| hub.pushScope() | `Sentry.withScope()` | +| hub.popScope() | `Sentry.withScope()` | +| hub.withScope() | `Sentry.withScope()` | +| getClient() | `Sentry.getClient()` | +| getScope() | `Sentry.getCurrentScope()` to get the currently active scope | +| getIsolationScope() | `Sentry.getIsolationScope()` | +| getStack() | REMOVED - The stack used to hold scopes. Scopes are used directly now | +| getStackTop() | REMOVED - The stack used to hold scopes. Scopes are used directly now | +| captureException() | `Sentry.captureException()` | +| captureMessage() | `Sentry.captureMessage()` | +| captureEvent() | `Sentry.captureEvent()` | +| lastEventId() | REMOVED - Use event processors or beforeSend instead | +| addBreadcrumb() | `Sentry.addBreadcrumb()` | +| setUser() | `Sentry.setUser()` | +| setTags() | `Sentry.setTags()` | +| setExtras() | `Sentry.setExtras()` | +| setTag() | `Sentry.setTag()` | +| setExtra() | `Sentry.setExtra()` | +| setContext() | `Sentry.setContext()` | +| configureScope() | REMOVED - Scopes are now the unit of concurrency | +| run() | `Sentry.withScope()` or `Sentry.withIsolationScope()` | +| getIntegration() | `client.getIntegration()` | +| startTransaction() | `Sentry.startSpan()`, `Sentry.startInactiveSpan()` or `Sentry.startSpanManual()` | +| traceHeaders() | REMOVED - The closest equivalent is now `spanToTraceHeader(getActiveSpan())` | +| captureSession() | `Sentry.captureSession()` | +| startSession() | `Sentry.startSession()` | +| endSession() | `Sentry.endSession()` | +| shouldSendDefaultPii() | REMOVED - The closest equivalent is `Sentry.getClient().getOptions().sendDefaultPii` | -Given you have a `Vue` application running, in order to use the `Vue` integration you need to do the following: +The `Hub` constructor is also deprecated and will be removed in the next major version. If you are creating Hubs for +multi-client use like so: -With `4.x`: +```ts +// OLD +const hub = new Hub(); +hub.bindClient(client); +makeMain(hub); +``` -```js -import * as Sentry from '@sentry/browser'; +instead initialize the client as follows: -Sentry.init({ - dsn: '___PUBLIC_DSN___', - integrations: [ - new Sentry.Integrations.Vue({ - Vue, - attachProps: true, - }), - ], +```ts +// NEW +Sentry.withIsolationScope(() => { + Sentry.setCurrentClient(client); + client.init(); }); ``` -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'; +If you are using the Hub to capture events like so: -Sentry.init({ - dsn: '___PUBLIC_DSN___', - integrations: [ - new Integrations.Vue({ - Vue, - attachProps: true, - }), - ], -}); +```ts +// OLD +const client = new Client(); +const hub = new Hub(client); +hub.captureException(); ``` -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: +instead capture isolated events as follows: -```html - - - - - - - - +```ts +// NEW +const client = new Client(); +const scope = new Scope(); +scope.setClient(client); +scope.captureException(); ``` -## 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`. +## Deprecate `client.setupIntegrations()` -```typescript -setTags(tags: { [key: string]: string | number | boolean | null | undefined }): this; -setExtras(extras: { [key: string]: any }): this; -clearBreadcrumbs(): this; -``` +Instead, use the new `client.init()` method. You should probably not use this directly and instead use `Sentry.init()`, +which calls this under the hood. But if you have a special use case that requires that, you can call `client.init()` +instead now. -So you can do this now: +## Deprecate `scope.getSpan()` and `scope.setSpan()` -```js -// New in 5.x setExtras -Sentry.withScope(scope => { - scope.setExtras(errorInfo); - Sentry.captureException(error); -}); +Instead, you can get the currently active span via `Sentry.getActiveSpan()`. Setting a span on the scope happens +automatically when you use the new performance APIs `startSpan()` and `startSpanManual()`. -// vs. 4.x -Sentry.withScope(scope => { - Object.keys(errorInfo).forEach(key => { - scope.setExtra(key, errorInfo[key]); - }); - Sentry.captureException(error); -}); -``` +## Deprecate `scope.setTransactionName()` -## Less Async API +Instead, either set this as attributes or tags, or use an event processor to set `event.transaction`. -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. +## Deprecate `scope.getTransaction()` and `getActiveTransaction()` -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`. +Instead, you should not rely on the active transaction, but just use `startSpan()` APIs, which handle this for you. -## `close` vs. `flush` +## Deprecate arguments for `startSpan()` APIs -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. +In v8, the API to start a new span will be reduced from the currently available options. Going forward, only these +argument will be passable to `startSpan()`, `startSpanManual()` and `startInactiveSpan()`: -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. +- `name` +- `attributes` +- `origin` +- `op` +- `startTime` +- `scope` -# Migrating from `raven-js` to `@sentry/browser` +## Deprecate `startTransaction()` & `span.startChild()` -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. +In v8, the old performance API `startTransaction()` (and `hub.startTransaction()`), as well as `span.startChild()`, will +be removed. Instead, use the new performance APIs: -#### Installation +- `startSpan()` +- `startSpanManual()` +- `startInactiveSpan()` -> [Docs](https://docs.sentry.io/platforms/javascript/#connecting-the-sdk-to-sentry) +You can [read more about the new performance APIs here](./docs/v8-new-performance-apis.md). -_Old_: +## Deprecate variations of `Sentry.continueTrace()` -```js -Raven.config('___PUBLIC_DSN___', { - release: '1.3.0', -}).install(); -``` +The version of `Sentry.continueTrace()` which does not take a callback argument will be removed in favor of the version +that does. Additionally, the callback argument will not receive an argument with the next major version. -_New_: +Use `Sentry.continueTrace()` as follows: -```js -Sentry.init({ - dsn: '___PUBLIC_DSN___', - release: '1.3.0', +```ts +app.get('/your-route', req => { + Sentry.withIsolationScope(isolationScope => { + Sentry.continueTrace( + { + sentryTrace: req.headers.get('sentry-trace'), + baggage: req.headers.get('baggage'), + }, + () => { + // All events recorded in this callback will be associated with the incoming trace. For example: + Sentry.startSpan({ name: '/my-route' }, async () => { + await doExpensiveWork(); + }); + }, + ); + }); }); ``` -#### Set a global tag - -> [Docs](https://docs.sentry.io/platforms/javascript/#tagging-events) +## Deprecate `Sentry.lastEventId()` and `hub.lastEventId()` -_Old_: +`Sentry.lastEventId()` sometimes causes race conditions, so we are deprecating it in favour of the `beforeSend` +callback. ```js -Raven.setTagsContext({ key: 'value' }); -``` +// Before +Sentry.init({ + beforeSend(event, hint) { + const lastCapturedEventId = Sentry.lastEventId(); -_New_: + // Do something with `lastCapturedEventId` here -```js -Sentry.setTag('key', 'value'); -``` + return event; + }, +}); -#### Set user context +// After +Sentry.init({ + beforeSend(event, hint) { + const lastCapturedEventId = event.event_id; -_Old_: + // Do something with `lastCapturedEventId` here -```js -Raven.setUserContext({ - id: '123', - email: 'david@example.com', + return event; + }, }); ``` -_New_: +## Deprecated fields on `Span` and `Transaction` -```js -Sentry.setUser({ - id: '123', - email: 'david@example.com', -}); -``` +In v8, the Span class is heavily reworked. The following properties & methods are thus deprecated: -#### Capture custom exception +- `span.toContext()`: Access the fields directly instead. +- `span.updateWithContext(newSpanContext)`: Update the fields directly instead. +- `span.setName(newName)`: Use `span.updateName(newName)` instead. +- `span.toTraceparent()`: use `spanToTraceHeader(span)` util instead. +- `span.getTraceContext()`: Use `spanToTraceContext(span)` utility function instead. +- `span.sampled`: Use `span.isRecording()` instead. +- `span.spanId`: Use `span.spanContext().spanId` instead. +- `span.parentSpanId`: Use `spanToJSON(span).parent_span_id` instead. +- `span.traceId`: Use `span.spanContext().traceId` instead. +- `span.name`: Use `spanToJSON(span).description` instead. +- `span.description`: Use `spanToJSON(span).description` instead. +- `span.getDynamicSamplingContext`: Use `getDynamicSamplingContextFromSpan` utility function instead. +- `span.tags`: Set tags on the surrounding scope instead, or use attributes. +- `span.data`: Use `spanToJSON(span).data` instead. +- `span.setTag()`: Use `span.setAttribute()` instead or set tags on the surrounding scope. +- `span.setData()`: Use `span.setAttribute()` instead. +- `span.instrumenter` This field was removed and will be replaced internally. +- `span.transaction`: Use `getRootSpan` utility function instead. +- `span.spanRecorder`: Span recording will be handled internally by the SDK. +- `span.status`: Use `.setStatus` to set or update and `spanToJSON()` to read the span status. +- `span.op`: Use `startSpan` functions to set, `setAttribute()` to update and `spanToJSON` to read the span operation. +- `span.isSuccess`: Use `spanToJSON(span).status === 'ok'` instead. +- `transaction.setMetadata()`: Use attributes instead, or set data on the scope. +- `transaction.metadata`: Use attributes instead, or set data on the scope. +- `transaction.setContext()`: Set context on the surrounding scope instead. +- `transaction.setMeasurement()`: Use `Sentry.setMeasurement()` instead. In v8, setting measurements will be limited to + the currently active root span. +- `transaction.setName()`: Set the name with `.updateName()` and the source with `.setAttribute()` instead. +- `span.startTimestamp`: use `spanToJSON(span).start_timestamp` instead. You cannot update this anymore in v8. +- `span.endTimestamp`: use `spanToJSON(span).timestamp` instead. You cannot update this anymore in v8. You can pass a + custom end timestamp to `span.end(endTimestamp)`. -> A scope must now be sent around a capture to add extra information. -> [Docs](https://docs.sentry.io/platforms/javascript/#unsetting-context) +## Deprecate `pushScope` & `popScope` in favor of `withScope` -_Old_: +Instead of manually pushing/popping a scope, you should use `Sentry.withScope(callback: (scope: Scope))` instead. -```js -try { - throwingFunction(); -} catch (e) { - Raven.captureException(e, { extra: { debug: false } }); -} -``` +## Deprecate `configureScope` in favor of using `getCurrentScope()` -_New_: +Instead of updating the scope in a callback via `configureScope()`, you should access it via `getCurrentScope()` and +configure it directly: ```js -try { - throwingFunction(); -} catch (e) { - Sentry.withScope(scope => { - scope.setExtra('debug', false); - Sentry.captureException(e); - }); -} +Sentry.getCurrentScope().setTag('xx', 'yy'); ``` -#### Capture a message +## Deprecate `addGlobalEventProcessor` in favor of `addEventProcessor` -> A scope must now be sent around a capture to add extra information. -> [Docs](https://docs.sentry.io/platforms/javascript/#unsetting-context) +Instead of using `addGlobalEventProcessor`, you should use `addEventProcessor` which does not add the event processor +globally, but to the current client. -_Old_: +For the vast majority of cases, the behavior of these should be the same. Only in the case where you have multiple +clients will this differ - but you'll likely want to add event processors per-client then anyhow, not globally. -```js -Raven.captureMessage('test1', 'info'); -Raven.captureMessage('test2', 'info', { extra: { debug: false } }); -``` +In v8, we will remove the global event processors overall, as that allows us to avoid keeping global state that is not +necessary. -_New_: +## Deprecate `extractTraceParentData` export from `@sentry/core` & downstream packages -```js -Sentry.captureMessage('test1', 'info'); -Sentry.withScope(scope => { - scope.setExtra('debug', false); - Sentry.captureMessage('test2', 'info'); -}); -``` +Instead, import this directly from `@sentry/utils`. -#### Breadcrumbs +Generally, in most cases you should probably use `continueTrace` instead, which abstracts this away from you and handles +scope propagation for you. -> [Docs](https://docs.sentry.io/platforms/javascript/#breadcrumbs) +## Deprecate `lastEventId()` -_Old_: +Instead, if you need the ID of a recently captured event, we recommend using `beforeSend` instead: -```js -Raven.captureBreadcrumb({ - message: 'Item added to shopping cart', - category: 'action', - data: { - isbn: '978-1617290541', - cartSize: '3', - }, -}); -``` +```ts +import * as Sentry from '@sentry/browser'; -_New_: +Sentry.init({ + dsn: '__DSN__', + beforeSend(event, hint) { + const lastCapturedEventId = event.event_id; -```js -Sentry.addBreadcrumb({ - message: 'Item added to shopping cart', - category: 'action', - data: { - isbn: '978-1617290541', - cartSize: '3', + // Do something with `lastCapturedEventId` here + + return event; }, }); ``` -### Ignoring Urls +## Deprecate `timestampWithMs` export - #7878 + +The `timestampWithMs` util is deprecated in favor of using `timestampInSeconds`. -> '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) +## `addTracingExtensions` replaces `addExtensionMethods` (since 7.46.0) -_Old_: +Since the deprecation of `@sentry/tracing`, tracing extensions are now added by calling `addTracingExtensions` which is +exported from all framework SDKs. ```js -Raven.config('___PUBLIC_DSN___', { - ignoreUrls: ['https://www.baddomain.com', /graph\.facebook\.com/i], +// Before +import * as Sentry from '@sentry/browser'; +import { addExtensionMethods } from '@sentry/tracing'; + +Sentry.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, }); -``` -_New_: +addExtensionMethods(); + +// After +import * as Sentry from '@sentry/browser'; -```js Sentry.init({ - denyUrls: ['https://www.baddomain.com', /graph\.facebook\.com/i], + dsn: '__DSN__', + tracesSampleRate: 1.0, }); + +Sentry.addTracingExtensions(); ``` -### Ignoring Events (`shouldSendCallback`) +## Remove requirement for `@sentry/tracing` package (since 7.46.0) -> `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) +With `7.46.0` you no longer require the `@sentry/tracing` package to use tracing and performance monitoring with the +Sentry JavaScript SDKs. The `@sentry/tracing` package will be removed in a future major release, but can still be used +in the meantime. -_Old_: +#### Browser: ```js -Raven.config('___PUBLIC_DSN___', { - shouldSendCallback(event) { - // Only send events that include user data - if (event.user) { - return true; - } - return false; - }, +// Before +import * as Sentry from '@sentry/browser'; +import { BrowserTracing } from '@sentry/tracing'; + +Sentry.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, + integrations: [new BrowserTracing()], }); -``` -_New_: +// After +import * as Sentry from '@sentry/browser'; -```js Sentry.init({ - beforeSend(event) { - if (event.user) { - return event; - } - return null; - }, + dsn: '__DSN__', + tracesSampleRate: 1.0, + integrations: [new Sentry.BrowserTracing()], }); ``` -### Modifying Events (`dataCallback`) - -_Old_: +#### Node: ```js -Raven.config('___PUBLIC_DSN___', { - dataCallback(event) { - if (event.user) { - // Don't send user's email address - delete event.user.email; - } - return event; - }, +// Before +const Sentry = require('@sentry/node'); +require('@sentry/tracing'); + +Sentry.init({ + dsn: '__DSN__', + tracesSampleRate: 1.0, }); -``` -_New_: +// After +const Sentry = require('@sentry/node'); -```js Sentry.init({ - beforeSend(event) { - if (event.user) { - delete event.user.email; - } - return event; - }, + dsn: '__DSN__', + tracesSampleRate: 1.0, + integrations: [ + // Automatically instrument Node.js libraries and frameworks + ...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations(), + ], }); ``` -### Attaching Stacktraces +**Note:** If you imported `stripUrlQueryAndFragment` from `@sentry/tracing`, you'll need to import it from +`@sentry/utils`, once you remove `@sentry/tracing`. + +## Replay options changed (since 7.35.0) - #6645 -> 'stacktrace' was renamed to 'attachStacktrace'. -> [Docs](https://docs.sentry.io/error-reporting/configuration/?platform=browser#attach-stacktrace) +Some options for replay have been deprecated in favor of new APIs. See +[Replay Migration docs](./packages/replay/MIGRATION.md#upgrading-replay-from-7340-to-7350) for details. -_Old_: +## Renaming of Next.js wrapper methods (since 7.31.0) - #6790 -```js -Raven.config('___PUBLIC_DSN___', { - stacktrace: true, -}); -``` +We updated the names of the functions to wrap data fetchers and API routes to better reflect what they are doing. The +old methods can still be used but are deprecated and will be removed in the next major update of the SDK. -_New_: +Following function names were updated: -```js -Sentry.init({ - attachStacktrace: true, -}); -``` +- `withSentryAPI` was renamed to `wrapApiHandlerWithSentry` +- `withSentryGetServerSideProps` was renamed to `wrapGetServerSidePropsWithSentry` +- `withSentryGetStaticProps` was renamed to `wrapGetStaticPropsWithSentry` +- `withSentryServerSideGetInitialProps` was renamed to `wrapGetInitialPropsWithSentry` +- `withSentryServerSideAppGetInitialProps` was renamed to `wrapAppGetInitialPropsWithSentry` +- `withSentryServerSideDocumentGetInitialProps` was renamed to `wrapDocumentGetInitialPropsWithSentry` +- `withSentryServerSideErrorGetInitialProps` was renamed to `wrapErrorGetInitialPropsWithSentry` -### Disabling Promises Handling +## Deprecated `tracingOrigins` (since 7.19.0) - #6176 -_Old_: +The `tracingOrigins` option is deprecated in favor of using `shouldCreateSpanForRequest` and `tracePropagationTargets`. -```js -Raven.config('___PUBLIC_DSN___', { - captureUnhandledRejections: false, -}); -``` +## Deprecate `componentTrackingPreprocessor` in Svelte SDK (since 7.16.0) - #5936 -_New_: +This release adds the `withSentryConfig` feature to the Svelte SDK. It replaces the now deprecated Svelte +`componentTrackingPreprocessor` which will be removed in the next major release. -```js -Sentry.init({ - integrations: [ - new Sentry.Integrations.GlobalHandlers({ - onunhandledrejection: false, - }), - ], -}); -``` +## Deprecate `getGlobalObject` in `@sentry/utils` (since 7.16.0) - #5949 + +This is no longer used. + +## Deprecate @sentry/hub (since 7.15.0) - #5823 + +This release deprecates `@sentry/hub` and all of it's exports. All of the `@sentry/hub` exports have moved to +`@sentry/core`. `@sentry/hub` will be removed in the next major release. + +# Upgrading Sentry Replay (beta, 7.24.0) + +For details on upgrading Replay in its beta phase, please view the +[dedicated Replay MIGRATION docs](./packages/replay/MIGRATION.md). 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/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/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" +``` + 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/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/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/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(); + }); + }); }); 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/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/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/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/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 b484e9c964ca..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. @@ -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 */ @@ -298,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/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`'], 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 f4ea6dd09846..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', () => { @@ -50,18 +52,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 +68,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 +77,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..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); }); @@ -232,12 +235,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 +418,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..bd298fd4d5ff 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', @@ -278,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); @@ -556,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(); @@ -780,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(); @@ -944,7 +946,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/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(), 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; } 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/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-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 { 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/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/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/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-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..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'; @@ -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, - }); + }) as SentrySpan; + sentrySpan.setStatus(mapStatus(span)); node.children.forEach(child => { createAndFinishSpanForOtelSpan(child, sentrySpan, remaining); 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(); 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/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/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; }, 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; } 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/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/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/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/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/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 dfc5ad5f9e6f..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, .... @@ -58,8 +57,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,13 +89,17 @@ 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 } -/** 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. @@ -108,12 +111,6 @@ export interface SpanContext { */ op?: string | undefined; - /** - * Completion status of the Span. - * See: {SpanStatusType} for possible values - */ - status?: string | undefined; - /** * Parent Span ID */ @@ -167,78 +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; - - /** - * Completion status of the Span. - * - * See: {SpanStatusType} for possible values - * - * @deprecated Use `.setStatus` to set or update and `spanToJSON()` to read the status. - */ - status?: string | undefined; - +/** + * A generic Span which holds trace data. + */ +export interface Span { /** * Get context data for this span. * This includes the spanId & the traceId. @@ -250,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/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/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); + } + }); + } } /** 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', }, }, 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,