diff --git a/.craft.yml b/.craft.yml index 9259fc979356..522b6f125f00 100644 --- a/.craft.yml +++ b/.craft.yml @@ -91,9 +91,6 @@ targets: - name: npm id: '@sentry/serverless' includeNames: /^sentry-serverless-\d.*\.tgz$/ - - name: npm - id: '@sentry/opentelemetry-node' - includeNames: /^sentry-opentelemetry-node-\d.*\.tgz$/ - name: npm id: '@sentry/bun' includeNames: /^sentry-bun-\d.*\.tgz$/ @@ -194,8 +191,6 @@ targets: onlyIfPresent: /^sentry-svelte-\d.*\.tgz$/ 'npm:@sentry/sveltekit': onlyIfPresent: /^sentry-sveltekit-\d.*\.tgz$/ - 'npm:@sentry/opentelemetry-node': - onlyIfPresent: /^sentry-opentelemetry-node-\d.*\.tgz$/ 'npm:@sentry/bun': onlyIfPresent: /^sentry-bun-\d.*\.tgz$/ 'npm:@sentry/vercel-edge': diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index 61c24641c292..9c8ca1f159b5 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -31,7 +31,7 @@ body: setup. options: - '@sentry/browser' - - '@sentry/astro' + - '@sentry/astro' - '@sentry/angular' - '@sentry/angular-ivy' - '@sentry/bun' @@ -40,7 +40,6 @@ body: - '@sentry/gatsby' - '@sentry/nextjs' - '@sentry/node' - - '@sentry/opentelemetry-node' - '@sentry/react' - '@sentry/remix' - '@sentry/serverless' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f3d9ca91f25a..954734ad0ba0 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,7 +49,7 @@ env: ${{ github.workspace }}/packages/utils/esm BUILD_CACHE_KEY: build-cache-${{ github.event.inputs.commit || github.sha }} - BUILD_PROFILING_NODE_CACHE_TARBALL_KEY: profiling-node-tarball-${{ github.event.inputs.commit || github.sha }} + BUILD_CACHE_TARBALL_KEY: tarball-${{ github.event.inputs.commit || github.sha }} # GH will use the first restore-key it finds that matches # So it will start by looking for one from the same branch, else take the newest one it can find elsewhere @@ -83,7 +83,7 @@ jobs: echo "COMMIT_MESSAGE=$(git log -n 1 --pretty=format:%s $COMMIT_SHA)" >> $GITHUB_ENV - name: Determine changed packages - uses: dorny/paths-filter@v3.0.0 + uses: dorny/paths-filter@v3.0.1 id: changed with: filters: | @@ -402,15 +402,15 @@ jobs: uses: ./.github/actions/restore-cache env: DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }} - - name: Pack tarballs - # Profiling tarball is built separately as we assemble the precompiled binaries - run: yarn build:tarball --ignore @sentry/profiling-node - - name: Restore profiling tarball - uses: actions/cache/restore@v4 + - name: Extract Profiling Node Prebuilt Binaries + uses: actions/download-artifact@v3 with: - key: ${{ env.BUILD_PROFILING_NODE_CACHE_TARBALL_KEY }} - path: ${{ github.workspace }}/packages/*/*.tgz + name: profiling-node-binaries-${{ github.sha }} + path: ${{ github.workspace }}/packages/profiling-node/lib/ + + - name: Pack tarballs + run: yarn build:tarball - name: Archive artifacts uses: actions/upload-artifact@v4 @@ -985,8 +985,6 @@ jobs: key: nx-Linux-${{ github.ref }}-${{ env.HEAD_COMMIT }} # On develop branch, we want to _store_ the cache (so it can be used by other branches), but never _restore_ from it restore-keys: ${{ env.NX_CACHE_RESTORE_KEYS }} - - name: Build tarballs - run: yarn build:tarball --ignore @sentry/profiling-node # Rebuild profiling by compiling TS and pull the precompiled binary artifacts - name: Build Profiling Node @@ -1001,6 +999,7 @@ jobs: # https://github.com/actions/upload-artifact/issues/478 if: | (needs.job_get_metadata.outputs.changed_profiling_node_bindings == 'true') || + (needs.job_get_metadata.outputs.is_release == 'true') || (github.event_name != 'pull_request') uses: actions/download-artifact@v3 with: @@ -1008,14 +1007,14 @@ jobs: path: ${{ github.workspace }}/packages/profiling-node/lib/ - name: Build Profiling tarball - run: yarn build:tarball --scope @sentry/profiling-node + run: yarn build:tarball # End rebuild profiling - name: Stores tarballs in cache uses: actions/cache/save@v4 with: path: ${{ github.workspace }}/packages/*/*.tgz - key: ${{ env.BUILD_PROFILING_NODE_CACHE_TARBALL_KEY }} + key: ${{ env.BUILD_CACHE_TARBALL_KEY }} job_e2e_tests: name: E2E ${{ matrix.label || matrix.test-application }} Test @@ -1111,7 +1110,7 @@ jobs: uses: actions/cache/restore@v4 with: path: ${{ github.workspace }}/packages/*/*.tgz - key: ${{ env.BUILD_PROFILING_NODE_CACHE_TARBALL_KEY }} + key: ${{ env.BUILD_CACHE_TARBALL_KEY }} - name: Get node version id: versions @@ -1202,13 +1201,13 @@ jobs: with: name: profiling-node-binaries-${{ github.sha }} path: ${{ github.workspace }}/packages/profiling-node/lib/ - - name: Build Profiling tarball - run: yarn build:tarball --scope @sentry/profiling-node + - name: Restore tarball cache uses: actions/cache/restore@v4 with: path: ${{ github.workspace }}/packages/*/*.tgz - key: ${{ env.BUILD_PROFILING_NODE_CACHE_TARBALL_KEY }} + key: ${{ env.BUILD_CACHE_TARBALL_KEY }} + fail-on-cache-miss : true - name: Get node version id: versions diff --git a/.github/workflows/canary.yml b/.github/workflows/canary.yml index 9a856a7ce034..9d34f7878785 100644 --- a/.github/workflows/canary.yml +++ b/.github/workflows/canary.yml @@ -135,7 +135,7 @@ jobs: - name: Create Issue if: failure() && github.event_name == 'schedule' - uses: JasonEtco/create-an-issue@e27dddc79c92bc6e4562f268fffa5ed752639abd + uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RUN_LINK: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} @@ -176,7 +176,7 @@ jobs: - name: Create Issue if: failure() && github.event_name == 'schedule' - uses: JasonEtco/create-an-issue@e27dddc79c92bc6e4562f268fffa5ed752639abd + uses: JasonEtco/create-an-issue@1b14a70e4d8dc185e5cc76d3bec9eab20257b2c5 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RUN_LINK: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} diff --git a/.github/workflows/flaky-test-detector.yml b/.github/workflows/flaky-test-detector.yml index 6c27000c6914..4643827dcebf 100644 --- a/.github/workflows/flaky-test-detector.yml +++ b/.github/workflows/flaky-test-detector.yml @@ -71,7 +71,7 @@ jobs: run: npx playwright install-deps - name: Determine changed tests - uses: dorny/paths-filter@v3.0.0 + uses: dorny/paths-filter@v3.0.1 id: changed with: list-files: json diff --git a/.github/workflows/issue-package-label.yml b/.github/workflows/issue-package-label.yml index 1c496c927762..c45f0e8359bc 100644 --- a/.github/workflows/issue-package-label.yml +++ b/.github/workflows/issue-package-label.yml @@ -56,9 +56,6 @@ jobs: "@sentry.node": { "label": "Package: Node" }, - "@sentry.opentelemetry-node": { - "label": "Package: otel-node" - }, "@sentry.react": { "label": "Package: react" }, diff --git a/CHANGELOG.md b/CHANGELOG.md index 237a8f79099f..bfb8eacad610 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,39 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +## 8.0.0-alpha.2 + +This alpha release fixes a build problem that prevented 8.0.0-alpha.1 from being properly released. + +### Important Changes + +- **feat: Remove `@sentry/opentelemetry-node` package (#10906)** + +The `@sentry/opentelemetry-node` package has been removed. Instead, you can either use `@sentry/node` with built-in +OpenTelemetry support, or use `@sentry/opentelemetry` to manually connect Sentry with OpenTelemetry. + +### Removal/Refactoring of deprecated functionality + +- ref: Refactor some deprecated `startSpan` options (#10825) +- feat(v8/core): remove void from transport return (#10794) +- ref(integrations): Delete deprecated class integrations (#10887) + +### Other Changes + +- feat(core): Use serialized spans in transaction event (#10912) +- feat(deps): bump @sentry/cli from 2.28.6 to 2.29.1 (#10908) +- feat(node): Allow to configure `skipOpenTelemetrySetup` (#10907) +- feat(esm): Import rather than require `inspector` (#10910) +- fix(browser): Don't use chrome variable name (#10874) +- chore(sveltekit): Fix punctuation in a console.log (#10895) +- fix(opentelemetry): Ensure DSC propagation works correctly (#10904) +- feat(browser): Exclude span exports from non-performance CDN bundles (#10879) +- ref: Refactor span status handling to be OTEL compatible (#10871) +- feat(core): Fix span scope handling & transaction setting (#10886) +- ref(ember): Avoid namespace import to hopefully resolve minification issue (#10885) + +Work in this release contributed by @harish-talview & @bfontaine. Thank you for your contributions! + ## 8.0.0-alpha.1 This is the first Alpha release of the v8 cycle, which includes a variety of breaking changes. @@ -19,7 +52,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 +154,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 +246,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 +262,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 aae2f3ee4292..8b5d1bdf0c0e 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -118,7 +118,7 @@ Sentry.init({ tracesSampleRate: 1.0, }); -// Before (v8) +// After (v8) const Sentry = require('@sentry/node'); Sentry.init({ @@ -272,6 +272,7 @@ Removed top-level exports: `tracingOrigins`, `MetricsAggregator`, `metricsAggreg - [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) +- [Remove `void` from transport return types](./MIGRATION.md#remove-void-from-transport-return-types) #### Deprecation of `Hub` and `getCurrentHub()` @@ -435,6 +436,23 @@ addEventProcessor(event => { The `lastEventId` function has been removed. See [below](./MIGRATION.md#deprecate-lasteventid) for more details. +#### Remove `void` from transport return types + +The `send` method on the `Transport` interface now always requires a `TransportMakeRequestResponse` to be returned in +the promise. This means that the `void` return type is no longer allowed. + +```ts +// Before (v7) +interface Transport { + send(event: Event): Promise; +} + +// After (v8) +interface Transport { + send(event: Event): Promise; +} +``` + ### Browser SDK (Browser, React, Vue, Angular, Ember, etc.) Removed top-level exports: `Offline`, `makeXHRTransport`, `BrowserTracing` diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/init.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/init.js index 9bd2d9649ed8..36806d01c6d0 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/dom/init.js @@ -5,6 +5,6 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', defaultIntegrations: false, - integrations: [new Sentry.Integrations.Breadcrumbs()], + integrations: [Sentry.breadcrumbsIntegration()], sampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/init.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/init.js index 9bd2d9649ed8..36806d01c6d0 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/fetch/init.js @@ -5,6 +5,6 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', defaultIntegrations: false, - integrations: [new Sentry.Integrations.Breadcrumbs()], + integrations: [Sentry.breadcrumbsIntegration()], sampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/init.js b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/init.js index 9bd2d9649ed8..36806d01c6d0 100644 --- a/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/init.js +++ b/dev-packages/browser-integration-tests/suites/integrations/Breadcrumbs/xhr/init.js @@ -5,6 +5,6 @@ window.Sentry = Sentry; Sentry.init({ dsn: 'https://public@dsn.ingest.sentry.io/1337', defaultIntegrations: false, - integrations: [new Sentry.Integrations.Breadcrumbs()], + integrations: [Sentry.breadcrumbsIntegration()], sampleRate: 1, }); diff --git a/dev-packages/browser-integration-tests/suites/manual-client/browser-context/init.js b/dev-packages/browser-integration-tests/suites/manual-client/browser-context/init.js index cf70853184cd..33777734fe84 100644 --- a/dev-packages/browser-integration-tests/suites/manual-client/browser-context/init.js +++ b/dev-packages/browser-integration-tests/suites/manual-client/browser-context/init.js @@ -1,23 +1,23 @@ import { - Breadcrumbs, BrowserClient, - FunctionToString, - HttpContext, Hub, - InboundFilters, - LinkedErrors, + breadcrumbsIntegration, dedupeIntegration, defaultStackParser, + functionToStringIntegration, + httpContextIntegration, + inboundFiltersIntegration, + linkedErrorsIntegration, makeFetchTransport, } from '@sentry/browser'; const integrations = [ - new Breadcrumbs(), - new FunctionToString(), + breadcrumbsIntegration(), + functionToStringIntegration(), dedupeIntegration(), - new HttpContext(), - new InboundFilters(), - new LinkedErrors(), + httpContextIntegration(), + inboundFiltersIntegration(), + linkedErrorsIntegration(), ]; const client = new BrowserClient({ diff --git a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts index 2567f5c018d3..71637c9294f1 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startSpan/basic/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -10,7 +10,7 @@ sentryTest('should send a transaction in an envelope', async ({ getLocalTestPath } const url = await getLocalTestPath({ testDir: __dirname }); - const transaction = await getFirstSentryEnvelopeRequest(page, url); + const transaction = await getFirstSentryEnvelopeRequest(page, url); expect(transaction.transaction).toBe('parent_span'); expect(transaction.spans).toBeDefined(); @@ -22,7 +22,7 @@ sentryTest('should report finished spans as children of the root transaction', a } const url = await getLocalTestPath({ testDir: __dirname }); - const transaction = await getFirstSentryEnvelopeRequest(page, url); + const transaction = await getFirstSentryEnvelopeRequest(page, url); expect(transaction.spans).toHaveLength(1); diff --git a/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/test.ts b/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/test.ts index 1f9897d2e680..0624ff3f7dad 100644 --- a/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/test.ts +++ b/dev-packages/browser-integration-tests/suites/public-api/startTransaction/basic_usage/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -10,7 +10,7 @@ sentryTest('should report a transaction in an envelope', async ({ getLocalTestPa } const url = await getLocalTestPath({ testDir: __dirname }); - const transaction = await getFirstSentryEnvelopeRequest(page, url); + const transaction = await getFirstSentryEnvelopeRequest(page, url); expect(transaction.transaction).toBe('root_span'); expect(transaction.spans).toBeDefined(); @@ -22,7 +22,7 @@ sentryTest('should report finished spans as children of the root span', async ({ } const url = await getLocalTestPath({ testDir: __dirname }); - const transaction = await getFirstSentryEnvelopeRequest(page, url); + const transaction = await getFirstSentryEnvelopeRequest(page, url); const rootSpanId = transaction?.contexts?.trace?.span_id; diff --git a/dev-packages/browser-integration-tests/suites/replay/errors/errorModeCustomTransport/init.js b/dev-packages/browser-integration-tests/suites/replay/errors/errorModeCustomTransport/init.js deleted file mode 100644 index 934c6f5bca6a..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/errors/errorModeCustomTransport/init.js +++ /dev/null @@ -1,23 +0,0 @@ -import * as Sentry from '@sentry/browser'; - -window.Sentry = Sentry; -window.Replay = new Sentry.Replay({ - flushMinDelay: 200, - flushMaxDelay: 200, - minReplayDuration: 0, -}); - -Sentry.init({ - dsn: 'https://public@dsn.ingest.sentry.io/1337', - sampleRate: 1, - replaysSessionSampleRate: 0.0, - replaysOnErrorSampleRate: 1.0, - integrations: [window.Replay], - transport: options => { - const transport = new Sentry.makeFetchTransport(options); - - delete transport.send.__sentry__baseTransport__; - - return transport; - }, -}); diff --git a/dev-packages/browser-integration-tests/suites/replay/errors/errorModeCustomTransport/test.ts b/dev-packages/browser-integration-tests/suites/replay/errors/errorModeCustomTransport/test.ts deleted file mode 100644 index 81ff9f06cc78..000000000000 --- a/dev-packages/browser-integration-tests/suites/replay/errors/errorModeCustomTransport/test.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { expect } from '@playwright/test'; - -import { sentryTest } from '../../../../utils/fixtures'; -import { getReplaySnapshot, shouldSkipReplayTest, waitForReplayRequest } from '../../../../utils/replayHelpers'; - -sentryTest( - '[error-mode] should handle errors with custom transport', - async ({ getLocalTestPath, page, forceFlushReplay }) => { - if (shouldSkipReplayTest()) { - sentryTest.skip(); - } - - const promiseReq0 = waitForReplayRequest(page, 0); - const promiseReq1 = waitForReplayRequest(page, 1); - - let callsToSentry = 0; - - await page.route('https://dsn.ingest.sentry.io/**/*', route => { - callsToSentry++; - - return route.fulfill({ - // Only error out for error, then succeed - status: callsToSentry === 1 ? 422 : 200, - }); - }); - - const url = await getLocalTestPath({ testDir: __dirname }); - - await page.goto(url); - await forceFlushReplay(); - expect(callsToSentry).toEqual(0); - - await page.locator('#error').click(); - await promiseReq0; - - await forceFlushReplay(); - await promiseReq1; - - const replay = await getReplaySnapshot(page); - expect(replay.recordingMode).toBe('session'); - }, -); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/test.ts index 467507fa6f75..a8e7f9eec335 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/http-timings/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -23,7 +23,7 @@ sentryTest('should create fetch spans with http timing @firefox', async ({ brows const url = await getLocalTestPath({ testDir: __dirname }); - const envelopes = await getMultipleSentryEnvelopeRequests(page, 2, { url, timeout: 10000 }); + const envelopes = await getMultipleSentryEnvelopeRequests(page, 2, { url, timeout: 10000 }); const tracingEvent = envelopes[envelopes.length - 1]; // last envelope contains tracing data on all browsers const requestSpans = tracingEvent.spans?.filter(({ op }) => op === 'http.client'); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/test.ts index 50c095dbcc57..a66d447d92ec 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/interactions/test.ts @@ -1,6 +1,6 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { Event, Span, SpanContext, Transaction } from '@sentry/types'; +import type { Event, SpanContext, SpanJSON } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { @@ -9,8 +9,8 @@ import { shouldSkipTracingTest, } from '../../../../utils/helpers'; -type TransactionJSON = ReturnType & { - spans: ReturnType[]; +type TransactionJSON = SpanJSON & { + spans: SpanJSON[]; contexts: SpanContext; platform: string; type: string; diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/test.ts index d460d2883afd..6dab208d1c4e 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-disabled/test.ts @@ -1,6 +1,6 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -15,7 +15,7 @@ sentryTest('should not capture long task when flag is disabled.', async ({ brows const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const eventData = await getFirstSentryEnvelopeRequest(page, url); const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); expect(uiSpans?.length).toBe(0); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/test.ts index 1ed0bcda2a89..6189758c0340 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/long-tasks-enabled/test.ts @@ -1,6 +1,6 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -15,7 +15,7 @@ sentryTest('should capture long task.', async ({ browserName, getLocalTestPath, const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const eventData = await getFirstSentryEnvelopeRequest(page, url); const uiSpans = eventData.spans?.filter(({ op }) => op?.startsWith('ui')); expect(uiSpans?.length).toBeGreaterThan(0); diff --git a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithHeartbeatTimeout/test.ts b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithHeartbeatTimeout/test.ts index ead37d6f8662..f96495a69925 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithHeartbeatTimeout/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/browserTracingIntegration/pageloadWithHeartbeatTimeout/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -16,7 +16,7 @@ sentryTest( const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const eventData = await getFirstSentryEnvelopeRequest(page, url); expect(eventData.contexts?.trace?.op).toBe('pageload'); expect( diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts index 0730d2ba4645..b60cdce9703b 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-browser-spans/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -11,7 +11,7 @@ sentryTest('should add browser-related spans to pageload transaction', async ({ const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const eventData = await getFirstSentryEnvelopeRequest(page, url); const browserSpans = eventData.spans?.filter(({ op }) => op === 'browser'); // Spans `connect`, `cache` and `DNS` are not always inside `pageload` transaction. diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts index 40159d39ea25..e98cb5b3d9b2 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/pageload-resource-spans/test.ts @@ -1,6 +1,6 @@ import type { Route } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -17,7 +17,7 @@ sentryTest('should add resource spans to pageload transaction', async ({ getLoca const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const eventData = await getFirstSentryEnvelopeRequest(page, url); const resourceSpans = eventData.spans?.filter(({ op }) => op?.startsWith('resource')); // Webkit 16.0 (which is linked to Playwright 1.27.1) consistently creates 2 consectutive spans for `css`, diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts index 94402e4f6ab3..e70247230b15 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fid/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -16,7 +16,7 @@ sentryTest('should capture a FID vital.', async ({ browserName, getLocalTestPath // To trigger FID await page.locator('#fid-btn').click(); - const eventData = await getFirstSentryEnvelopeRequest(page); + const eventData = await getFirstSentryEnvelopeRequest(page); expect(eventData.measurements).toBeDefined(); expect(eventData.measurements?.fid?.value).toBeDefined(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts index 2ba417a84c18..70a9fd689e6e 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/metrics/web-vitals-fp-fcp/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -11,7 +11,7 @@ sentryTest('should capture FP vital.', async ({ browserName, getLocalTestPath, p } const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const eventData = await getFirstSentryEnvelopeRequest(page, url); expect(eventData.measurements).toBeDefined(); expect(eventData.measurements?.fp?.value).toBeDefined(); @@ -29,7 +29,7 @@ sentryTest('should capture FCP vital.', async ({ getLocalTestPath, page }) => { } const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const eventData = await getFirstSentryEnvelopeRequest(page, url); expect(eventData.measurements).toBeDefined(); expect(eventData.measurements?.fcp?.value).toBeDefined(); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/fetch/test.ts b/dev-packages/browser-integration-tests/suites/tracing/request/fetch/test.ts index ab4c29906f5c..4308491f2d7f 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/request/fetch/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/request/fetch/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getMultipleSentryEnvelopeRequests, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -21,7 +21,7 @@ sentryTest('should create spans for multiple fetch requests', async ({ getLocalT // If we are on FF or webkit: // 1st envelope contains CORS error // 2nd envelope contains the tracing data we want to check here - const envelopes = await getMultipleSentryEnvelopeRequests(page, 2, { url, timeout: 10000 }); + const envelopes = await getMultipleSentryEnvelopeRequests(page, 2, { url, timeout: 10000 }); const tracingEvent = envelopes[envelopes.length - 1]; // last envelope contains tracing data on all browsers const requestSpans = tracingEvent.spans?.filter(({ op }) => op === 'http.client'); diff --git a/dev-packages/browser-integration-tests/suites/tracing/request/xhr/test.ts b/dev-packages/browser-integration-tests/suites/tracing/request/xhr/test.ts index bd4ee0bbb003..3d329c9c5c6d 100644 --- a/dev-packages/browser-integration-tests/suites/tracing/request/xhr/test.ts +++ b/dev-packages/browser-integration-tests/suites/tracing/request/xhr/test.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import type { SerializedEvent } from '@sentry/types'; +import type { Event } from '@sentry/types'; import { sentryTest } from '../../../../utils/fixtures'; import { getFirstSentryEnvelopeRequest, shouldSkipTracingTest } from '../../../../utils/helpers'; @@ -11,7 +11,7 @@ sentryTest('should create spans for multiple XHR requests', async ({ getLocalTes const url = await getLocalTestPath({ testDir: __dirname }); - const eventData = await getFirstSentryEnvelopeRequest(page, url); + const eventData = await getFirstSentryEnvelopeRequest(page, url); const requestSpans = eventData.spans?.filter(({ op }) => op === 'http.client'); expect(requestSpans).toHaveLength(3); diff --git a/dev-packages/e2e-tests/test-applications/angular-17/event-proxy-server.ts b/dev-packages/e2e-tests/test-applications/angular-17/event-proxy-server.ts index 2a4dc2b525d9..d14ca5cb5e72 100644 --- a/dev-packages/e2e-tests/test-applications/angular-17/event-proxy-server.ts +++ b/dev-packages/e2e-tests/test-applications/angular-17/event-proxy-server.ts @@ -6,7 +6,7 @@ import * as os from 'os'; import * as path from 'path'; import * as util from 'util'; import * as zlib from 'zlib'; -import type { Envelope, EnvelopeItem, SerializedEvent } from '@sentry/types'; +import type { Envelope, EnvelopeItem, Event } from '@sentry/types'; import { parseEnvelope } from '@sentry/utils'; const readFile = util.promisify(fs.readFile); @@ -210,13 +210,13 @@ export function waitForEnvelopeItem( export function waitForError( proxyServerName: string, - callback: (transactionEvent: SerializedEvent) => Promise | boolean, -): Promise { + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { return new Promise((resolve, reject) => { waitForEnvelopeItem(proxyServerName, async envelopeItem => { const [envelopeItemHeader, envelopeItemBody] = envelopeItem; - if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as SerializedEvent))) { - resolve(envelopeItemBody as SerializedEvent); + if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); return true; } return false; @@ -226,13 +226,13 @@ export function waitForError( export function waitForTransaction( proxyServerName: string, - callback: (transactionEvent: SerializedEvent) => Promise | boolean, -): Promise { + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { return new Promise((resolve, reject) => { waitForEnvelopeItem(proxyServerName, async envelopeItem => { const [envelopeItemHeader, envelopeItemBody] = envelopeItem; - if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as SerializedEvent))) { - resolve(envelopeItemBody as SerializedEvent); + if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); return true; } return false; diff --git a/dev-packages/e2e-tests/test-applications/angular-17/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/angular-17/tests/errors.test.ts index d34f6a83eb29..e4d687ca3199 100644 --- a/dev-packages/e2e-tests/test-applications/angular-17/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/angular-17/tests/errors.test.ts @@ -3,7 +3,7 @@ import { waitForError } from '../event-proxy-server'; test('sends an error', async ({ page }) => { const errorPromise = waitForError('angular-17', async errorEvent => { - return !errorEvent?.transaction; + return !errorEvent.type; }); await page.goto(`/`); diff --git a/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts b/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts index 48776e59e268..241d82f715a0 100644 --- a/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts +++ b/dev-packages/e2e-tests/test-applications/generic-ts3.8/index.ts @@ -5,8 +5,6 @@ import * as _SentryCore from '@sentry/core'; // biome-ignore lint/nursery/noUnusedImports: import * as _SentryNode from '@sentry/node'; // biome-ignore lint/nursery/noUnusedImports: -import * as _SentryOpentelemetry from '@sentry/opentelemetry-node'; -// biome-ignore lint/nursery/noUnusedImports: import * as _SentryReplay from '@sentry/replay'; // biome-ignore lint/nursery/noUnusedImports: import * as _SentryTypes from '@sentry/types'; diff --git a/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json b/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json index 8e409a936199..acaa0a116ac0 100644 --- a/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json +++ b/dev-packages/e2e-tests/test-applications/generic-ts3.8/package.json @@ -16,7 +16,6 @@ "@sentry/browser": "latest || *", "@sentry/core": "latest || *", "@sentry/node": "latest || *", - "@sentry/opentelemetry-node": "latest || *", "@sentry/replay": "latest || *", "@sentry/types": "latest || *", "@sentry/utils": "latest || *", diff --git a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts index 560a5911522f..421914877ce2 100644 --- a/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts +++ b/dev-packages/e2e-tests/test-applications/nextjs-app-dir/tests/route-handlers.test.ts @@ -52,7 +52,7 @@ test('Should record exceptions and transactions for faulty route handlers', asyn expect(routehandlerTransaction.contexts?.trace?.op).toBe('http.server'); expect(routehandlerError.exception?.values?.[0].value).toBe('route-handler-error'); - expect(routehandlerError.tags?.transaction).toBe('PUT /route-handlers/[param]/error'); + expect(routehandlerError.transaction).toBe('PUT /route-handlers/[param]/error'); }); test.describe('Edge runtime', () => { diff --git a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts index 2a4dc2b525d9..d14ca5cb5e72 100644 --- a/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts +++ b/dev-packages/e2e-tests/test-applications/node-experimental-fastify-app/event-proxy-server.ts @@ -6,7 +6,7 @@ import * as os from 'os'; import * as path from 'path'; import * as util from 'util'; import * as zlib from 'zlib'; -import type { Envelope, EnvelopeItem, SerializedEvent } from '@sentry/types'; +import type { Envelope, EnvelopeItem, Event } from '@sentry/types'; import { parseEnvelope } from '@sentry/utils'; const readFile = util.promisify(fs.readFile); @@ -210,13 +210,13 @@ export function waitForEnvelopeItem( export function waitForError( proxyServerName: string, - callback: (transactionEvent: SerializedEvent) => Promise | boolean, -): Promise { + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { return new Promise((resolve, reject) => { waitForEnvelopeItem(proxyServerName, async envelopeItem => { const [envelopeItemHeader, envelopeItemBody] = envelopeItem; - if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as SerializedEvent))) { - resolve(envelopeItemBody as SerializedEvent); + if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); return true; } return false; @@ -226,13 +226,13 @@ export function waitForError( export function waitForTransaction( proxyServerName: string, - callback: (transactionEvent: SerializedEvent) => Promise | boolean, -): Promise { + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { return new Promise((resolve, reject) => { waitForEnvelopeItem(proxyServerName, async envelopeItem => { const [envelopeItemHeader, envelopeItemBody] = envelopeItem; - if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as SerializedEvent))) { - resolve(envelopeItemBody as SerializedEvent); + if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); return true; } return false; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/event-proxy-server.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2/event-proxy-server.ts index 2a4dc2b525d9..d14ca5cb5e72 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/event-proxy-server.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/event-proxy-server.ts @@ -6,7 +6,7 @@ import * as os from 'os'; import * as path from 'path'; import * as util from 'util'; import * as zlib from 'zlib'; -import type { Envelope, EnvelopeItem, SerializedEvent } from '@sentry/types'; +import type { Envelope, EnvelopeItem, Event } from '@sentry/types'; import { parseEnvelope } from '@sentry/utils'; const readFile = util.promisify(fs.readFile); @@ -210,13 +210,13 @@ export function waitForEnvelopeItem( export function waitForError( proxyServerName: string, - callback: (transactionEvent: SerializedEvent) => Promise | boolean, -): Promise { + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { return new Promise((resolve, reject) => { waitForEnvelopeItem(proxyServerName, async envelopeItem => { const [envelopeItemHeader, envelopeItemBody] = envelopeItem; - if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as SerializedEvent))) { - resolve(envelopeItemBody as SerializedEvent); + if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); return true; } return false; @@ -226,13 +226,13 @@ export function waitForError( export function waitForTransaction( proxyServerName: string, - callback: (transactionEvent: SerializedEvent) => Promise | boolean, -): Promise { + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { return new Promise((resolve, reject) => { waitForEnvelopeItem(proxyServerName, async envelopeItem => { const [envelopeItemHeader, envelopeItemBody] = envelopeItem; - if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as SerializedEvent))) { - resolve(envelopeItemBody as SerializedEvent); + if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); return true; } return false; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit-2/test/errors.server.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit-2/test/errors.server.test.ts index b7966325560a..972ee59955dd 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit-2/test/errors.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit-2/test/errors.server.test.ts @@ -60,9 +60,6 @@ test.describe('server-side errors', () => { }), ); - expect(errorEvent.tags).toMatchObject({ - runtime: 'node', - transaction: 'GET /server-route-error', - }); + expect(errorEvent.transaction).toEqual('GET /server-route-error'); }); }); diff --git a/dev-packages/e2e-tests/test-applications/sveltekit/event-proxy-server.ts b/dev-packages/e2e-tests/test-applications/sveltekit/event-proxy-server.ts index 11f00b4a2426..eba9ffef8682 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit/event-proxy-server.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit/event-proxy-server.ts @@ -6,7 +6,7 @@ import * as os from 'os'; import * as path from 'path'; import * as util from 'util'; import * as zlib from 'zlib'; -import type { Envelope, EnvelopeItem, Event, SerializedEvent } from '@sentry/types'; +import type { Envelope, EnvelopeItem, Event } from '@sentry/types'; import { parseEnvelope } from '@sentry/utils'; const readFile = util.promisify(fs.readFile); @@ -226,13 +226,13 @@ export function waitForError( export function waitForTransaction( proxyServerName: string, - callback: (transactionEvent: SerializedEvent) => Promise | boolean, -): Promise { + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { return new Promise((resolve, reject) => { waitForEnvelopeItem(proxyServerName, async envelopeItem => { const [envelopeItemHeader, envelopeItemBody] = envelopeItem; - if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as SerializedEvent))) { - resolve(envelopeItemBody as SerializedEvent); + if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); return true; } return false; diff --git a/dev-packages/e2e-tests/test-applications/sveltekit/test/errors.server.test.ts b/dev-packages/e2e-tests/test-applications/sveltekit/test/errors.server.test.ts index 5493659b19db..f2b7d2d21531 100644 --- a/dev-packages/e2e-tests/test-applications/sveltekit/test/errors.server.test.ts +++ b/dev-packages/e2e-tests/test-applications/sveltekit/test/errors.server.test.ts @@ -63,9 +63,6 @@ test.describe('server-side errors', () => { }), ); - expect(errorEvent.tags).toMatchObject({ - runtime: 'node', - transaction: 'GET /server-route-error', - }); + expect(errorEvent.transaction).toEqual('GET /server-route-error'); }); }); diff --git a/dev-packages/e2e-tests/test-applications/vue-3/event-proxy-server.ts b/dev-packages/e2e-tests/test-applications/vue-3/event-proxy-server.ts index 2a4dc2b525d9..d14ca5cb5e72 100644 --- a/dev-packages/e2e-tests/test-applications/vue-3/event-proxy-server.ts +++ b/dev-packages/e2e-tests/test-applications/vue-3/event-proxy-server.ts @@ -6,7 +6,7 @@ import * as os from 'os'; import * as path from 'path'; import * as util from 'util'; import * as zlib from 'zlib'; -import type { Envelope, EnvelopeItem, SerializedEvent } from '@sentry/types'; +import type { Envelope, EnvelopeItem, Event } from '@sentry/types'; import { parseEnvelope } from '@sentry/utils'; const readFile = util.promisify(fs.readFile); @@ -210,13 +210,13 @@ export function waitForEnvelopeItem( export function waitForError( proxyServerName: string, - callback: (transactionEvent: SerializedEvent) => Promise | boolean, -): Promise { + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { return new Promise((resolve, reject) => { waitForEnvelopeItem(proxyServerName, async envelopeItem => { const [envelopeItemHeader, envelopeItemBody] = envelopeItem; - if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as SerializedEvent))) { - resolve(envelopeItemBody as SerializedEvent); + if (envelopeItemHeader.type === 'event' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); return true; } return false; @@ -226,13 +226,13 @@ export function waitForError( export function waitForTransaction( proxyServerName: string, - callback: (transactionEvent: SerializedEvent) => Promise | boolean, -): Promise { + callback: (transactionEvent: Event) => Promise | boolean, +): Promise { return new Promise((resolve, reject) => { waitForEnvelopeItem(proxyServerName, async envelopeItem => { const [envelopeItemHeader, envelopeItemBody] = envelopeItem; - if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as SerializedEvent))) { - resolve(envelopeItemBody as SerializedEvent); + if (envelopeItemHeader.type === 'transaction' && (await callback(envelopeItemBody as Event))) { + resolve(envelopeItemBody as Event); return true; } return false; diff --git a/dev-packages/e2e-tests/test-applications/vue-3/tests/errors.test.ts b/dev-packages/e2e-tests/test-applications/vue-3/tests/errors.test.ts index 508fe738bbc5..b9933299c8c0 100644 --- a/dev-packages/e2e-tests/test-applications/vue-3/tests/errors.test.ts +++ b/dev-packages/e2e-tests/test-applications/vue-3/tests/errors.test.ts @@ -3,7 +3,7 @@ import { waitForError } from '../event-proxy-server'; test('sends an error', async ({ page }) => { const errorPromise = waitForError('vue-3', async errorEvent => { - return !errorEvent?.transaction; + return !errorEvent.type; }); await page.goto(`/`); diff --git a/dev-packages/e2e-tests/verdaccio-config/config.yaml b/dev-packages/e2e-tests/verdaccio-config/config.yaml index 042f93162934..c99f9def69e4 100644 --- a/dev-packages/e2e-tests/verdaccio-config/config.yaml +++ b/dev-packages/e2e-tests/verdaccio-config/config.yaml @@ -104,12 +104,6 @@ packages: unpublish: $all # proxy: npmjs # Don't proxy for E2E tests! - '@sentry/opentelemetry-node': - access: $all - publish: $all - unpublish: $all - # proxy: npmjs # Don't proxy for E2E tests! - '@sentry/opentelemetry': access: $all publish: $all diff --git a/dev-packages/node-integration-tests/src/index.ts b/dev-packages/node-integration-tests/src/index.ts index aba4e76caa7e..46c6e98401ae 100644 --- a/dev-packages/node-integration-tests/src/index.ts +++ b/dev-packages/node-integration-tests/src/index.ts @@ -7,7 +7,7 @@ import type { Express } from 'express'; */ export function loggingTransport(_options: BaseTransportOptions): Transport { return { - send(request: Envelope): Promise { + send(request: Envelope): Promise { // eslint-disable-next-line no-console console.log(JSON.stringify(request)); return Promise.resolve({ statusCode: 200 }); diff --git a/package.json b/package.json index b44b18e87a7a..0c39a0033abb 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "postpublish": "lerna run --stream --concurrency 1 postpublish", "test": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\" test", "test:unit": "lerna run --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\" test:unit", - "test-ci-browser": "lerna run test --ignore \"@sentry/{bun,deno,node,node-experimental,opentelemetry-node,profiling-node,serverless,nextjs,remix,gatsby,sveltekit,vercel-edge}\" --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\"", + "test-ci-browser": "lerna run test --ignore \"@sentry/{bun,deno,node,node-experimental,profiling-node,serverless,nextjs,remix,gatsby,sveltekit,vercel-edge}\" --ignore \"@sentry-internal/{browser-integration-tests,e2e-tests,integration-shims,node-integration-tests,overhead-metrics}\"", "test-ci-node": "ts-node ./scripts/node-unit-tests.ts", "test-ci-bun": "lerna run test --scope @sentry/bun", "test:update-snapshots": "lerna run test:update-snapshots", @@ -61,7 +61,6 @@ "packages/nextjs", "packages/node", "packages/node-experimental", - "packages/opentelemetry-node", "packages/opentelemetry", "packages/profiling-node", "packages/react", diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index b066ebe3d9a4..643235a2ec36 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -45,10 +45,6 @@ export { Scope, // eslint-disable-next-line deprecation/deprecation startTransaction, - getActiveSpan, - startSpan, - startInactiveSpan, - startSpanManual, continueTrace, SDK_VERSION, setContext, @@ -59,11 +55,6 @@ export { setUser, withScope, withIsolationScope, - withActiveSpan, - // eslint-disable-next-line deprecation/deprecation - FunctionToString, - // eslint-disable-next-line deprecation/deprecation - InboundFilters, functionToStringIntegration, inboundFiltersIntegration, dedupeIntegration, @@ -113,6 +104,3 @@ export { globalHandlersIntegration } from './integrations/globalhandlers'; export { httpContextIntegration } from './integrations/httpcontext'; export { linkedErrorsIntegration } from './integrations/linkederrors'; export { browserApiErrorsIntegration } from './integrations/browserapierrors'; - -// eslint-disable-next-line deprecation/deprecation -export { Breadcrumbs, LinkedErrors, HttpContext } from './integrations'; diff --git a/packages/browser/src/index.bundle.base.ts b/packages/browser/src/index.bundle.base.ts index 20bea46dca8b..5ccb0f1b1cf2 100644 --- a/packages/browser/src/index.bundle.base.ts +++ b/packages/browser/src/index.bundle.base.ts @@ -2,11 +2,9 @@ import type { IntegrationFn } from '@sentry/types/src'; export * from './exports'; -import { Integrations as CoreIntegrations } from '@sentry/core'; import type { Integration } from '@sentry/types'; import { WINDOW } from './helpers'; -import * as BrowserIntegrations from './integrations'; let windowIntegrations = {}; @@ -18,9 +16,6 @@ if (WINDOW.Sentry && WINDOW.Sentry.Integrations) { // eslint-disable-next-line @typescript-eslint/no-explicit-any const INTEGRATIONS: Record Integration) | IntegrationFn> = { ...windowIntegrations, - // eslint-disable-next-line deprecation/deprecation - ...CoreIntegrations, - ...BrowserIntegrations, }; export { INTEGRATIONS as Integrations }; diff --git a/packages/browser/src/index.bundle.tracing.replay.feedback.ts b/packages/browser/src/index.bundle.tracing.replay.feedback.ts index 0e75d5319ee2..2ccc69a8456f 100644 --- a/packages/browser/src/index.bundle.tracing.replay.feedback.ts +++ b/packages/browser/src/index.bundle.tracing.replay.feedback.ts @@ -14,6 +14,14 @@ Sentry.Integrations.Replay = Replay; // We are patching the global object with our hub extension methods addTracingExtensions(); +export { + getActiveSpan, + startSpan, + startInactiveSpan, + startSpanManual, + withActiveSpan, +} from '@sentry/core'; + export { // eslint-disable-next-line deprecation/deprecation Feedback, diff --git a/packages/browser/src/index.bundle.tracing.replay.ts b/packages/browser/src/index.bundle.tracing.replay.ts index 5b5fdef07bf6..8c7ac5bb917f 100644 --- a/packages/browser/src/index.bundle.tracing.replay.ts +++ b/packages/browser/src/index.bundle.tracing.replay.ts @@ -14,6 +14,14 @@ Sentry.Integrations.Replay = Replay; // We are patching the global object with our hub extension methods addTracingExtensions(); +export { + getActiveSpan, + startSpan, + startInactiveSpan, + startSpanManual, + withActiveSpan, +} from '@sentry/core'; + export { // eslint-disable-next-line deprecation/deprecation FeedbackShim as Feedback, diff --git a/packages/browser/src/index.bundle.tracing.ts b/packages/browser/src/index.bundle.tracing.ts index e3e75924c10f..c457db356d67 100644 --- a/packages/browser/src/index.bundle.tracing.ts +++ b/packages/browser/src/index.bundle.tracing.ts @@ -19,6 +19,14 @@ Sentry.Integrations.Replay = ReplayShim; // We are patching the global object with our hub extension methods addTracingExtensions(); +export { + getActiveSpan, + startSpan, + startInactiveSpan, + startSpanManual, + withActiveSpan, +} from '@sentry/core'; + export { // eslint-disable-next-line deprecation/deprecation FeedbackShim as Feedback, diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index d4c47e6f91eb..c6270e521188 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -1,9 +1,6 @@ export * from './exports'; -import { Integrations as CoreIntegrations } from '@sentry/core'; - import { WINDOW } from './helpers'; -import * as BrowserIntegrations from './integrations'; let windowIntegrations = {}; @@ -15,9 +12,6 @@ if (WINDOW.Sentry && WINDOW.Sentry.Integrations) { /** @deprecated Import the integration function directly, e.g. `inboundFiltersIntegration()` instead of `new Integrations.InboundFilter(). */ const INTEGRATIONS = { ...windowIntegrations, - // eslint-disable-next-line deprecation/deprecation - ...CoreIntegrations, - ...BrowserIntegrations, }; // eslint-disable-next-line deprecation/deprecation @@ -76,6 +70,11 @@ export { export type { RequestInstrumentationOptions } from '@sentry-internal/tracing'; export { addTracingExtensions, + getActiveSpan, + startSpan, + startInactiveSpan, + startSpanManual, + withActiveSpan, setMeasurement, // eslint-disable-next-line deprecation/deprecation getActiveTransaction, @@ -86,7 +85,6 @@ export { ModuleMetadata, moduleMetadataIntegration, } from '@sentry/core'; -export type { SpanStatusType } from '@sentry/core'; export type { Span } from '@sentry/types'; export { makeBrowserOfflineTransport } from './transports/offline'; export { browserProfilingIntegration } from './profiling/integration'; diff --git a/packages/browser/src/integrations/breadcrumbs.ts b/packages/browser/src/integrations/breadcrumbs.ts index efef3f8057cd..d9e149ae4a0d 100644 --- a/packages/browser/src/integrations/breadcrumbs.ts +++ b/packages/browser/src/integrations/breadcrumbs.ts @@ -1,5 +1,5 @@ /* eslint-disable max-lines */ -import { addBreadcrumb, convertIntegrationFnToClass, defineIntegration, getClient } from '@sentry/core'; +import { addBreadcrumb, defineIntegration, getClient } from '@sentry/core'; import type { Client, Event as SentryEvent, @@ -8,8 +8,6 @@ import type { HandlerDataFetch, HandlerDataHistory, HandlerDataXhr, - Integration, - IntegrationClass, IntegrationFn, } from '@sentry/types'; import type { @@ -95,32 +93,6 @@ const _breadcrumbsIntegration = ((options: Partial = {}) => export const breadcrumbsIntegration = defineIntegration(_breadcrumbsIntegration); -/** - * Default Breadcrumbs instrumentations - * - * @deprecated Use `breadcrumbsIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const Breadcrumbs = convertIntegrationFnToClass(INTEGRATION_NAME, breadcrumbsIntegration) as IntegrationClass< - Integration & { setup: (client: Client) => void } -> & { - new ( - options?: Partial<{ - console: boolean; - dom: - | boolean - | { - serializeAttribute?: string | string[]; - maxStringLength?: number; - }; - fetch: boolean; - history: boolean; - sentry: boolean; - xhr: boolean; - }>, - ): Integration; -}; - /** * Adds a breadcrumb for Sentry events or transactions if this option is enabled. */ diff --git a/packages/browser/src/integrations/index.ts b/packages/browser/src/integrations/index.ts deleted file mode 100644 index 66aa1d8f11a5..000000000000 --- a/packages/browser/src/integrations/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -export { Breadcrumbs } from './breadcrumbs'; -export { LinkedErrors } from './linkederrors'; -export { HttpContext } from './httpcontext'; diff --git a/packages/browser/src/stack-parsers.ts b/packages/browser/src/stack-parsers.ts index effe7538178b..5d09cde3cbbe 100644 --- a/packages/browser/src/stack-parsers.ts +++ b/packages/browser/src/stack-parsers.ts @@ -55,7 +55,9 @@ const chromeRegex = /^\s*at (?:(.+?\)(?: \[.+\])?|.*?) ?\((?:address at )?)?(?:async )?((?:|[-a-z]+:|.*bundle|\/)?.*?)(?::(\d+))?(?::(\d+))?\)?\s*$/i; const chromeEvalRegex = /\((\S*)(?::(\d+))(?::(\d+))\)/; -const chrome: StackLineParserFn = line => { +// We cannot call this variable `chrome` because it can conflict with global `chrome` variable in certain environments +// See: https://github.com/getsentry/sentry-javascript/issues/6880 +const chromeStackParserFn: StackLineParserFn = line => { const parts = chromeRegex.exec(line); if (parts) { @@ -82,7 +84,7 @@ const chrome: StackLineParserFn = line => { return; }; -export const chromeStackLineParser: StackLineParser = [CHROME_PRIORITY, chrome]; +export const chromeStackLineParser: StackLineParser = [CHROME_PRIORITY, chromeStackParserFn]; // gecko regex: `(?:bundle|\d+\.js)`: `bundle` is for react native, `\d+\.js` also but specifically for ram bundles because it // generates filenames without a prefix like `file://` the filenames in the stacktrace are just 42.js diff --git a/packages/browser/test/unit/index.test.ts b/packages/browser/test/unit/index.test.ts index a7e4cf6afd2a..19d8f570f9e6 100644 --- a/packages/browser/test/unit/index.test.ts +++ b/packages/browser/test/unit/index.test.ts @@ -1,4 +1,10 @@ -import { InboundFilters, SDK_VERSION, getGlobalScope, getIsolationScope, getReportDialogEndpoint } from '@sentry/core'; +import { + SDK_VERSION, + getGlobalScope, + getIsolationScope, + getReportDialogEndpoint, + inboundFiltersIntegration, +} from '@sentry/core'; import type { WrappedFunction } from '@sentry/types'; import * as utils from '@sentry/utils'; @@ -270,10 +276,7 @@ describe('SentryBrowser', () => { const options = getDefaultBrowserClientOptions({ beforeSend: localBeforeSend, dsn, - integrations: [ - // eslint-disable-next-line deprecation/deprecation - new InboundFilters({ ignoreErrors: ['capture'] }), - ], + integrations: [inboundFiltersIntegration({ ignoreErrors: ['capture'] })], }); const client = new BrowserClient(options); setCurrentClient(client); diff --git a/packages/browser/test/unit/integrations/breadcrumbs.test.ts b/packages/browser/test/unit/integrations/breadcrumbs.test.ts index 28764c2cddfa..3c9713a872d2 100644 --- a/packages/browser/test/unit/integrations/breadcrumbs.test.ts +++ b/packages/browser/test/unit/integrations/breadcrumbs.test.ts @@ -1,6 +1,6 @@ import * as SentryCore from '@sentry/core'; -import { Breadcrumbs, BrowserClient, flush } from '../../../src'; +import { BrowserClient, breadcrumbsIntegration, flush } from '../../../src'; import { getDefaultBrowserClientOptions } from '../helper/browser-client-options'; describe('Breadcrumbs', () => { @@ -8,8 +8,7 @@ describe('Breadcrumbs', () => { const client = new BrowserClient({ ...getDefaultBrowserClientOptions(), dsn: 'https://username@domain/123', - // eslint-disable-next-line deprecation/deprecation - integrations: [new Breadcrumbs()], + integrations: [breadcrumbsIntegration()], }); SentryCore.setCurrentClient(client); diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index feb114d4723f..80ad94b8e460 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -85,7 +85,6 @@ export { captureSession, endSession, } from '@sentry/core'; -export type { SpanStatusType } from '@sentry/core'; export { DEFAULT_USER_INCLUDES, autoDiscoverNodePerformanceMonitoringIntegrations, @@ -119,14 +118,11 @@ export { init, } from './sdk'; -import { Integrations as CoreIntegrations } from '@sentry/core'; import { Integrations as NodeIntegrations } from '@sentry/node-experimental'; import { BunServer } from './integrations/bunserver'; export { bunServerIntegration } from './integrations/bunserver'; const INTEGRATIONS = { - // eslint-disable-next-line deprecation/deprecation - ...CoreIntegrations, ...NodeIntegrations, BunServer, }; diff --git a/packages/bun/src/integrations/bunserver.ts b/packages/bun/src/integrations/bunserver.ts index 5075d2f8f250..f5b6a52d3dd0 100644 --- a/packages/bun/src/integrations/bunserver.ts +++ b/packages/bun/src/integrations/bunserver.ts @@ -11,7 +11,7 @@ import { startSpan, withIsolationScope, } from '@sentry/core'; -import type { IntegrationFn } from '@sentry/types'; +import type { IntegrationFn, SpanAttributes } from '@sentry/types'; import { getSanitizedUrlString, parseUrl } from '@sentry/utils'; const INTEGRATION_NAME = 'BunServer'; @@ -53,7 +53,7 @@ export function instrumentBunServe(): void { function instrumentBunServeOptions(serveOptions: Parameters[0]): void { serveOptions.fetch = new Proxy(serveOptions.fetch, { apply(fetchTarget, fetchThisArg, fetchArgs: Parameters) { - return withIsolationScope(() => { + return withIsolationScope(isolationScope => { const request = fetchArgs[0]; const upperCaseMethod = request.method.toUpperCase(); if (upperCaseMethod === 'OPTIONS' || upperCaseMethod === 'HEAD') { @@ -61,37 +61,34 @@ function instrumentBunServeOptions(serveOptions: Parameters[0] } const parsedUrl = parseUrl(request.url); - const data: Record = { + const attributes: SpanAttributes = { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.bun.serve', 'http.request.method': request.method || 'GET', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', }; if (parsedUrl.search) { - data['http.query'] = parsedUrl.search; + attributes['http.query'] = parsedUrl.search; } const url = getSanitizedUrlString(parsedUrl); + isolationScope.setSDKProcessingMetadata({ + request: { + url, + method: request.method, + headers: request.headers.toJSON(), + }, + }); + return continueTrace( { sentryTrace: request.headers.get('sentry-trace') || '', baggage: request.headers.get('baggage') }, ctx => { return startSpan( { - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.http.bun.serve', - }, + attributes, op: 'http.server', name: `${request.method} ${parsedUrl.path || '/'}`, ...ctx, - data, - metadata: { - // eslint-disable-next-line deprecation/deprecation - ...ctx.metadata, - request: { - url, - method: request.method, - headers: request.headers.toJSON(), - }, - }, }, async span => { try { diff --git a/packages/core/src/baseclient.ts b/packages/core/src/baseclient.ts index 21341bef1e42..8d36844a96bf 100644 --- a/packages/core/src/baseclient.ts +++ b/packages/core/src/baseclient.ts @@ -432,10 +432,7 @@ export abstract class BaseClient implements Client { public on(hook: 'preprocessEvent', callback: (event: Event, hint?: EventHint) => void): void; /** @inheritdoc */ - public on( - hook: 'afterSendEvent', - callback: (event: Event, sendResponse: TransportMakeRequestResponse | void) => void, - ): void; + public on(hook: 'afterSendEvent', callback: (event: Event, sendResponse: TransportMakeRequestResponse) => void): void; /** @inheritdoc */ public on(hook: 'beforeAddBreadcrumb', callback: (breadcrumb: Breadcrumb, hint?: BreadcrumbHint) => void): void; @@ -443,9 +440,6 @@ export abstract class BaseClient implements Client { /** @inheritdoc */ public on(hook: 'createDsc', callback: (dsc: DynamicSamplingContext) => void): void; - /** @inheritdoc */ - public on(hook: 'otelSpanEnd', callback: (otelSpan: unknown, mutableOptions: { drop: boolean }) => void): void; - /** @inheritdoc */ public on( hook: 'beforeSendFeedback', @@ -488,7 +482,7 @@ export abstract class BaseClient implements Client { public emit(hook: 'preprocessEvent', event: Event, hint?: EventHint): void; /** @inheritdoc */ - public emit(hook: 'afterSendEvent', event: Event, sendResponse: TransportMakeRequestResponse | void): void; + public emit(hook: 'afterSendEvent', event: Event, sendResponse: TransportMakeRequestResponse): void; /** @inheritdoc */ public emit(hook: 'beforeAddBreadcrumb', breadcrumb: Breadcrumb, hint?: BreadcrumbHint): void; @@ -496,9 +490,6 @@ export abstract class BaseClient implements Client { /** @inheritdoc */ public emit(hook: 'createDsc', dsc: DynamicSamplingContext): void; - /** @inheritdoc */ - public emit(hook: 'otelSpanEnd', otelSpan: unknown, mutableOptions: { drop: boolean }): void; - /** @inheritdoc */ public emit(hook: 'beforeSendFeedback', feedback: FeedbackEvent, options?: { includeReplay: boolean }): void; @@ -524,16 +515,17 @@ export abstract class BaseClient implements Client { /** * @inheritdoc */ - public sendEnvelope(envelope: Envelope): PromiseLike | void { + public sendEnvelope(envelope: Envelope): PromiseLike | void { this.emit('beforeEnvelope', envelope); if (this._isEnabled() && this._transport) { return this._transport.send(envelope).then(null, reason => { DEBUG_BUILD && logger.error('Error while sending event:', reason); + return reason; }); - } else { - DEBUG_BUILD && logger.error('Transport disabled'); } + + DEBUG_BUILD && logger.error('Transport disabled'); } /* eslint-enable @typescript-eslint/unified-signatures */ diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index b5bee258dae6..b8c451b432cd 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -97,12 +97,7 @@ export { DEFAULT_ENVIRONMENT } from './constants'; /* eslint-disable deprecation/deprecation */ export { ModuleMetadata } from './integrations/metadata'; export { RequestData } from './integrations/requestdata'; -export { InboundFilters } from './integrations/inboundfilters'; -export { FunctionToString } from './integrations/functiontostring'; -export { LinkedErrors } from './integrations/linkederrors'; export { addBreadcrumb } from './breadcrumbs'; -/* eslint-enable deprecation/deprecation */ -import * as INTEGRATIONS from './integrations'; export { functionToStringIntegration } from './integrations/functiontostring'; export { inboundFiltersIntegration } from './integrations/inboundfilters'; export { linkedErrorsIntegration } from './integrations/linkederrors'; @@ -118,9 +113,3 @@ export { metrics } from './metrics/exports'; export type { MetricData } from './metrics/exports'; export { metricsDefault } from './metrics/exports-default'; export { BrowserMetricsAggregator } from './metrics/browser-aggregator'; - -/** @deprecated Import the integration function directly, e.g. `inboundFiltersIntegration()` instead of `new Integrations.InboundFilter(). */ -const Integrations = INTEGRATIONS; - -// eslint-disable-next-line deprecation/deprecation -export { Integrations }; diff --git a/packages/core/src/integrations/inboundfilters.ts b/packages/core/src/integrations/inboundfilters.ts index ac89c7d222c6..833169e33bd2 100644 --- a/packages/core/src/integrations/inboundfilters.ts +++ b/packages/core/src/integrations/inboundfilters.ts @@ -1,8 +1,8 @@ -import type { Client, Event, EventHint, Integration, IntegrationClass, IntegrationFn, StackFrame } from '@sentry/types'; +import type { Event, IntegrationFn, StackFrame } from '@sentry/types'; import { getEventDescription, logger, stringMatchesSomePattern } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; -import { convertIntegrationFnToClass, defineIntegration } from '../integration'; +import { 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. @@ -36,28 +36,6 @@ const _inboundFiltersIntegration = ((options: Partial = { export const inboundFiltersIntegration = defineIntegration(_inboundFiltersIntegration); -/** - * Inbound filters configurable by the user. - * @deprecated Use `inboundFiltersIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const InboundFilters = convertIntegrationFnToClass( - INTEGRATION_NAME, - inboundFiltersIntegration, -) as IntegrationClass void }> & { - new ( - options?: Partial<{ - allowUrls: Array; - denyUrls: Array; - ignoreErrors: Array; - ignoreTransactions: Array; - ignoreInternal: boolean; - disableErrorDefaults: boolean; - disableTransactionDefaults: boolean; - }>, - ): Integration; -}; - function _mergeOptions( internalOptions: Partial = {}, clientOptions: Partial = {}, diff --git a/packages/core/src/integrations/index.ts b/packages/core/src/integrations/index.ts deleted file mode 100644 index 7e2dc7fa5094..000000000000 --- a/packages/core/src/integrations/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -/* eslint-disable deprecation/deprecation */ -export { FunctionToString } from './functiontostring'; -export { InboundFilters } from './inboundfilters'; -export { LinkedErrors } from './linkederrors'; diff --git a/packages/core/src/integrations/linkederrors.ts b/packages/core/src/integrations/linkederrors.ts index b23eaf4272b0..d5d4d03f9e90 100644 --- a/packages/core/src/integrations/linkederrors.ts +++ b/packages/core/src/integrations/linkederrors.ts @@ -1,6 +1,6 @@ -import type { Client, Event, EventHint, Integration, IntegrationClass, IntegrationFn } from '@sentry/types'; +import type { IntegrationFn } from '@sentry/types'; import { applyAggregateErrorsToEvent, exceptionFromError } from '@sentry/utils'; -import { convertIntegrationFnToClass, defineIntegration } from '../integration'; +import { defineIntegration } from '../integration'; interface LinkedErrorsOptions { key?: string; @@ -35,12 +35,3 @@ const _linkedErrorsIntegration = ((options: LinkedErrorsOptions = {}) => { }) satisfies IntegrationFn; export const linkedErrorsIntegration = defineIntegration(_linkedErrorsIntegration); - -/** - * Adds SDK info to an event. - * @deprecated Use `linkedErrorsIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const LinkedErrors = convertIntegrationFnToClass(INTEGRATION_NAME, linkedErrorsIntegration) as IntegrationClass< - Integration & { preprocessEvent: (event: Event, hint: EventHint, client: Client) => void } -> & { new (options?: { key?: string; limit?: number }): Integration }; diff --git a/packages/core/src/tracing/errors.ts b/packages/core/src/tracing/errors.ts index f93486129c35..2645c10e42b8 100644 --- a/packages/core/src/tracing/errors.ts +++ b/packages/core/src/tracing/errors.ts @@ -5,7 +5,7 @@ import { } from '@sentry/utils'; import { DEBUG_BUILD } from '../debug-build'; -import type { SpanStatusType } from './spanstatus'; +import { SPAN_STATUS_ERROR } from './spanstatus'; import { getActiveTransaction } from './utils'; let errorsInstrumented = false; @@ -35,9 +35,9 @@ function errorCallback(): void { // eslint-disable-next-line deprecation/deprecation const activeTransaction = getActiveTransaction(); if (activeTransaction) { - const status: SpanStatusType = 'internal_error'; - DEBUG_BUILD && logger.log(`[Tracing] Transaction: ${status} -> Global error occured`); - activeTransaction.setStatus(status); + const message = 'internal_error'; + DEBUG_BUILD && logger.log(`[Tracing] Transaction: ${message} -> Global error occured`); + activeTransaction.setStatus({ code: SPAN_STATUS_ERROR, message }); } } diff --git a/packages/core/src/tracing/idletransaction.ts b/packages/core/src/tracing/idletransaction.ts index 8d0db5d92762..0aad33ca6836 100644 --- a/packages/core/src/tracing/idletransaction.ts +++ b/packages/core/src/tracing/idletransaction.ts @@ -5,6 +5,7 @@ import { DEBUG_BUILD } from '../debug-build'; import { spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils'; import type { SentrySpan } from './sentrySpan'; import { SpanRecorder } from './sentrySpan'; +import { SPAN_STATUS_ERROR } from './spanstatus'; import { Transaction } from './transaction'; export const TRACING_DEFAULTS = { @@ -147,7 +148,7 @@ export class IdleTransaction extends Transaction { setTimeout(() => { if (!this._finished) { - this.setStatus('deadline_exceeded'); + this.setStatus({ code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' }); this._finishReason = IDLE_TRANSACTION_FINISH_REASONS[3]; this.end(); } @@ -185,7 +186,7 @@ export class IdleTransaction extends Transaction { // We cancel all pending spans with status "cancelled" to indicate the idle transaction was finished early if (!spanToJSON(span).timestamp) { - span.setStatus('cancelled'); + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'cancelled' }); span.end(endTimestampInS); DEBUG_BUILD && logger.log('[Tracing] cancelling span since transaction ended early', JSON.stringify(span, undefined, 2)); @@ -396,7 +397,7 @@ export class IdleTransaction extends Transaction { if (this._heartbeatCounter >= 3) { if (this._autoFinishAllowed) { DEBUG_BUILD && logger.log('[Tracing] Transaction finished because of no change for 3 heart beats'); - this.setStatus('deadline_exceeded'); + this.setStatus({ code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' }); this._finishReason = IDLE_TRANSACTION_FINISH_REASONS[0]; this.end(); } diff --git a/packages/core/src/tracing/index.ts b/packages/core/src/tracing/index.ts index f8284fe16e29..9ca8f26eac3f 100644 --- a/packages/core/src/tracing/index.ts +++ b/packages/core/src/tracing/index.ts @@ -5,13 +5,11 @@ export { SentrySpan } from './sentrySpan'; export { Transaction } from './transaction'; // eslint-disable-next-line deprecation/deprecation export { getActiveTransaction, getActiveSpan } from './utils'; -// eslint-disable-next-line deprecation/deprecation -export { SpanStatus } from './spanstatus'; export { setHttpStatus, getSpanStatusFromHttpCode, } from './spanstatus'; -export type { SpanStatusType } from './spanstatus'; +export { SPAN_STATUS_ERROR, SPAN_STATUS_OK, SPAN_STATUS_UNSET } from './spanstatus'; export { startSpan, startInactiveSpan, diff --git a/packages/core/src/tracing/sentrySpan.ts b/packages/core/src/tracing/sentrySpan.ts index 9d1587d5fb60..b0e82321e33e 100644 --- a/packages/core/src/tracing/sentrySpan.ts +++ b/packages/core/src/tracing/sentrySpan.ts @@ -7,6 +7,7 @@ import type { SpanContextData, SpanJSON, SpanOrigin, + SpanStatus, SpanTimeInput, TraceContext, Transaction, @@ -24,7 +25,7 @@ import { spanToJSON, spanToTraceContext, } from '../utils/spanUtils'; -import type { SpanStatusType } from './spanstatus'; +import { SPAN_STATUS_OK, SPAN_STATUS_UNSET } from './spanstatus'; import { addChildSpanToSpan } from './utils'; /** @@ -99,7 +100,7 @@ export class SentrySpan implements Span { /** Epoch timestamp in seconds when the span ended. */ protected _endTime?: number | undefined; /** Internal keeper of the status */ - protected _status?: SpanStatusType | string | undefined; + protected _status?: SpanStatus; private _logMessage?: string; @@ -363,7 +364,7 @@ export class SentrySpan implements Span { /** * @inheritDoc */ - public setStatus(value: SpanStatusType): this { + public setStatus(value: SpanStatus): this { this._status = value; return this; } @@ -445,7 +446,7 @@ export class SentrySpan implements Span { parent_span_id: this._parentSpanId, span_id: this._spanId, start_timestamp: this._startTime, - status: this._status, + status: getStatusMessage(this._status), // eslint-disable-next-line deprecation/deprecation tags: Object.keys(this.tags).length > 0 ? this.tags : undefined, timestamp: this._endTime, @@ -499,3 +500,15 @@ export class SentrySpan implements Span { return hasData ? data : attributes; } } + +function getStatusMessage(status: SpanStatus | undefined): string | undefined { + if (!status || status.code === SPAN_STATUS_UNSET) { + return undefined; + } + + if (status.code === SPAN_STATUS_OK) { + return 'ok'; + } + + return status.message || 'unknown_error'; +} diff --git a/packages/core/src/tracing/spanstatus.ts b/packages/core/src/tracing/spanstatus.ts index 3855fda0307e..b1fd046f10c3 100644 --- a/packages/core/src/tracing/spanstatus.ts +++ b/packages/core/src/tracing/spanstatus.ts @@ -1,126 +1,53 @@ -import type { Span } from '@sentry/types'; +import type { Span, SpanStatus } from '@sentry/types'; -/** The status of an Span. - * - * @deprecated Use string literals - if you require type casting, cast to SpanStatusType type - */ -export enum SpanStatus { - /** The operation completed successfully. */ - Ok = 'ok', - /** Deadline expired before operation could complete. */ - DeadlineExceeded = 'deadline_exceeded', - /** 401 Unauthorized (actually does mean unauthenticated according to RFC 7235) */ - Unauthenticated = 'unauthenticated', - /** 403 Forbidden */ - PermissionDenied = 'permission_denied', - /** 404 Not Found. Some requested entity (file or directory) was not found. */ - NotFound = 'not_found', - /** 429 Too Many Requests */ - ResourceExhausted = 'resource_exhausted', - /** Client specified an invalid argument. 4xx. */ - InvalidArgument = 'invalid_argument', - /** 501 Not Implemented */ - Unimplemented = 'unimplemented', - /** 503 Service Unavailable */ - Unavailable = 'unavailable', - /** Other/generic 5xx. */ - InternalError = 'internal_error', - /** Unknown. Any non-standard HTTP status code. */ - UnknownError = 'unknown_error', - /** The operation was cancelled (typically by the user). */ - Cancelled = 'cancelled', - /** Already exists (409) */ - AlreadyExists = 'already_exists', - /** Operation was rejected because the system is not in a state required for the operation's */ - FailedPrecondition = 'failed_precondition', - /** The operation was aborted, typically due to a concurrency issue. */ - Aborted = 'aborted', - /** Operation was attempted past the valid range. */ - OutOfRange = 'out_of_range', - /** Unrecoverable data loss or corruption */ - DataLoss = 'data_loss', -} - -export type SpanStatusType = - /** The operation completed successfully. */ - | 'ok' - /** Deadline expired before operation could complete. */ - | 'deadline_exceeded' - /** 401 Unauthorized (actually does mean unauthenticated according to RFC 7235) */ - | 'unauthenticated' - /** 403 Forbidden */ - | 'permission_denied' - /** 404 Not Found. Some requested entity (file or directory) was not found. */ - | 'not_found' - /** 429 Too Many Requests */ - | 'resource_exhausted' - /** Client specified an invalid argument. 4xx. */ - | 'invalid_argument' - /** 501 Not Implemented */ - | 'unimplemented' - /** 503 Service Unavailable */ - | 'unavailable' - /** Other/generic 5xx. */ - | 'internal_error' - /** Unknown. Any non-standard HTTP status code. */ - | 'unknown_error' - /** The operation was cancelled (typically by the user). */ - | 'cancelled' - /** Already exists (409) */ - | 'already_exists' - /** Operation was rejected because the system is not in a state required for the operation's */ - | 'failed_precondition' - /** The operation was aborted, typically due to a concurrency issue. */ - | 'aborted' - /** Operation was attempted past the valid range. */ - | 'out_of_range' - /** Unrecoverable data loss or corruption */ - | 'data_loss'; +export const SPAN_STATUS_UNSET = 0; +export const SPAN_STATUS_OK = 1; +export const SPAN_STATUS_ERROR = 2; /** - * Converts a HTTP status code into a {@link SpanStatusType}. + * Converts a HTTP status code into a sentry status with a message. * * @param httpStatus The HTTP response status code. * @returns The span status or unknown_error. */ -export function getSpanStatusFromHttpCode(httpStatus: number): SpanStatusType { +export function getSpanStatusFromHttpCode(httpStatus: number): SpanStatus { if (httpStatus < 400 && httpStatus >= 100) { - return 'ok'; + return { code: SPAN_STATUS_OK }; } if (httpStatus >= 400 && httpStatus < 500) { switch (httpStatus) { case 401: - return 'unauthenticated'; + return { code: SPAN_STATUS_ERROR, message: 'unauthenticated' }; case 403: - return 'permission_denied'; + return { code: SPAN_STATUS_ERROR, message: 'permission_denied' }; case 404: - return 'not_found'; + return { code: SPAN_STATUS_ERROR, message: 'not_found' }; case 409: - return 'already_exists'; + return { code: SPAN_STATUS_ERROR, message: 'already_exists' }; case 413: - return 'failed_precondition'; + return { code: SPAN_STATUS_ERROR, message: 'failed_precondition' }; case 429: - return 'resource_exhausted'; + return { code: SPAN_STATUS_ERROR, message: 'resource_exhausted' }; default: - return 'invalid_argument'; + return { code: SPAN_STATUS_ERROR, message: 'invalid_argument' }; } } if (httpStatus >= 500 && httpStatus < 600) { switch (httpStatus) { case 501: - return 'unimplemented'; + return { code: SPAN_STATUS_ERROR, message: 'unimplemented' }; case 503: - return 'unavailable'; + return { code: SPAN_STATUS_ERROR, message: 'unavailable' }; case 504: - return 'deadline_exceeded'; + return { code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' }; default: - return 'internal_error'; + return { code: SPAN_STATUS_ERROR, message: 'internal_error' }; } } - return 'unknown_error'; + return { code: SPAN_STATUS_ERROR, message: 'unknown_error' }; } /** @@ -131,7 +58,7 @@ export function setHttpStatus(span: Span, httpStatus: number): void { span.setAttribute('http.response.status_code', httpStatus); const spanStatus = getSpanStatusFromHttpCode(httpStatus); - if (spanStatus !== 'unknown_error') { + if (spanStatus.message !== 'unknown_error') { span.setStatus(spanStatus); } } diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index 561f61ce9265..98cd511493c6 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -10,6 +10,7 @@ import { hasTracingEnabled } from '../utils/hasTracingEnabled'; import { spanIsSampled, spanTimeInputToSeconds, spanToJSON } from '../utils/spanUtils'; import { getDynamicSamplingContextFromSpan } from './dynamicSamplingContext'; import type { SentrySpan } from './sentrySpan'; +import { SPAN_STATUS_ERROR } from './spanstatus'; import { addChildSpanToSpan, getActiveSpan, setCapturedScopesOnSpan } from './utils'; /** @@ -42,6 +43,11 @@ export function startSpan(context: StartSpanOptions, callback: (span: Span | scope, }); + if (activeSpan) { + // eslint-disable-next-line deprecation/deprecation + scope.setSpan(activeSpan); + } + return handleCallbackErrors( () => callback(activeSpan), () => { @@ -49,7 +55,7 @@ export function startSpan(context: StartSpanOptions, callback: (span: Span | if (activeSpan) { const { status } = spanToJSON(activeSpan); if (!status || status === 'ok') { - activeSpan.setStatus('internal_error'); + activeSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); } } }, @@ -91,6 +97,11 @@ export function startSpanManual( scope, }); + if (activeSpan) { + // eslint-disable-next-line deprecation/deprecation + scope.setSpan(activeSpan); + } + function finishAndSetSpan(): void { activeSpan && activeSpan.end(); } @@ -102,7 +113,7 @@ export function startSpanManual( if (activeSpan && activeSpan.isRecording()) { const { status } = spanToJSON(activeSpan); if (!status || status === 'ok') { - activeSpan.setStatus('internal_error'); + activeSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); } } }, @@ -141,16 +152,11 @@ export function startInactiveSpan(context: StartSpanOptions): Span | undefined { const scope = context.scope || getCurrentScope(); - // Even though we don't actually want to make this span active on the current scope, - // we need to make it active on a temporary scope that we use for event processing - // as otherwise, it won't pick the correct span for the event when processing it - const temporaryScope = scope.clone(); - return createChildSpanOrTransaction(hub, { parentSpan, spanContext, forceTransaction: context.forceTransaction, - scope: temporaryScope, + scope, }); } @@ -319,12 +325,6 @@ function createChildSpanOrTransaction( }); } - // We always set this as active span on the scope - // In the case of this being an inactive span, we ensure to pass a detached scope in here in the first place - // But by having this here, we can ensure that the lookup through `getCapturedScopesOnSpan` results in the correct scope & span combo - // eslint-disable-next-line deprecation/deprecation - scope.setSpan(span); - setCapturedScopesOnSpan(span, scope, isolationScope); return span; diff --git a/packages/core/src/tracing/transaction.ts b/packages/core/src/tracing/transaction.ts index 3c0c114f1061..9f4194681338 100644 --- a/packages/core/src/tracing/transaction.ts +++ b/packages/core/src/tracing/transaction.ts @@ -276,8 +276,7 @@ export class Transaction extends SentrySpan implements TransactionInterface { // We don't want to override trace context trace: spanToTraceContext(this), }, - // TODO: Pass spans serialized via `spanToJSON()` here instead in v8. - spans: finishedSpans, + spans: finishedSpans.map(span => spanToJSON(span)), start_timestamp: this._startTime, // eslint-disable-next-line deprecation/deprecation tags: this.tags, diff --git a/packages/core/src/transports/base.ts b/packages/core/src/transports/base.ts index 2121ea6751f7..b942266457f9 100644 --- a/packages/core/src/transports/base.ts +++ b/packages/core/src/transports/base.ts @@ -37,14 +37,14 @@ export const DEFAULT_TRANSPORT_BUFFER_SIZE = 30; export function createTransport( options: InternalBaseTransportOptions, makeRequest: TransportRequestExecutor, - buffer: PromiseBuffer = makePromiseBuffer( + buffer: PromiseBuffer = makePromiseBuffer( options.bufferSize || DEFAULT_TRANSPORT_BUFFER_SIZE, ), ): Transport { let rateLimits: RateLimits = {}; const flush = (timeout?: number): PromiseLike => buffer.drain(timeout); - function send(envelope: Envelope): PromiseLike { + function send(envelope: Envelope): PromiseLike { const filteredEnvelopeItems: EnvelopeItem[] = []; // Drop rate limited items from envelope @@ -60,7 +60,7 @@ export function createTransport( // Skip sending if envelope is empty after filtering out rate limited events if (filteredEnvelopeItems.length === 0) { - return resolvedSyncPromise(); + return resolvedSyncPromise({}); } // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -74,7 +74,7 @@ export function createTransport( }); }; - const requestTask = (): PromiseLike => + const requestTask = (): PromiseLike => makeRequest({ body: serializeEnvelope(filteredEnvelope) }).then( response => { // We don't want to throw on NOK responses, but we want to at least log them @@ -97,7 +97,7 @@ export function createTransport( if (error instanceof SentryError) { DEBUG_BUILD && logger.error('Skipped sending event because buffer is full.'); recordEnvelopeLoss('queue_overflow'); - return resolvedSyncPromise(); + return resolvedSyncPromise({}); } else { throw error; } @@ -105,10 +105,6 @@ export function createTransport( ); } - // We use this to identifify if the transport is the base transport - // TODO (v8): Remove this again as we'll no longer need it - send.__sentry__baseTransport__ = true; - return { send, flush, diff --git a/packages/core/src/transports/multiplexed.ts b/packages/core/src/transports/multiplexed.ts index a496a8adcd6f..faeb5a0fbd81 100644 --- a/packages/core/src/transports/multiplexed.ts +++ b/packages/core/src/transports/multiplexed.ts @@ -57,7 +57,7 @@ function makeOverrideReleaseTransport( const transport = createTransport(options); return { - send: async (envelope: Envelope): Promise => { + send: async (envelope: Envelope): Promise => { const event = eventFromEnvelope(envelope, ['event', 'transaction', 'profile', 'replay_event']); if (event) { @@ -101,7 +101,7 @@ export function makeMultiplexedTransport( return otherTransports[key]; } - async function send(envelope: Envelope): Promise { + async function send(envelope: Envelope): Promise { function getEvent(types?: EnvelopeItemType[]): Event | undefined { const eventTypes: EnvelopeItemType[] = types && types.length ? types : ['event']; return eventFromEnvelope(envelope, eventTypes); diff --git a/packages/core/src/transports/offline.ts b/packages/core/src/transports/offline.ts index c628e538a071..2e0db450ddfd 100644 --- a/packages/core/src/transports/offline.ts +++ b/packages/core/src/transports/offline.ts @@ -113,7 +113,7 @@ export function makeOfflineTransport( retryDelay = Math.min(retryDelay * 2, MAX_DELAY); } - async function send(envelope: Envelope): Promise { + async function send(envelope: Envelope): Promise { try { const result = await transport.send(envelope); diff --git a/packages/core/src/utils/applyScopeDataToEvent.ts b/packages/core/src/utils/applyScopeDataToEvent.ts index 92a5e6747cd8..2d0d20661fe2 100644 --- a/packages/core/src/utils/applyScopeDataToEvent.ts +++ b/packages/core/src/utils/applyScopeDataToEvent.ts @@ -178,9 +178,10 @@ function applySpanToEvent(event: Event, span: Span): void { dynamicSamplingContext: getDynamicSamplingContextFromSpan(span), ...event.sdkProcessingMetadata, }; + const transactionName = spanToJSON(rootSpan).description; - if (transactionName) { - event.tags = { transaction: transactionName, ...event.tags }; + if (transactionName && !event.transaction) { + event.transaction = transactionName; } } } diff --git a/packages/core/src/utils/prepareEvent.ts b/packages/core/src/utils/prepareEvent.ts index 006ef75a5f13..92cc5ffa65fa 100644 --- a/packages/core/src/utils/prepareEvent.ts +++ b/packages/core/src/utils/prepareEvent.ts @@ -16,7 +16,6 @@ import { getGlobalScope } from '../currentScopes'; import { getGlobalEventProcessors, notifyEventProcessors } from '../eventProcessors'; import { Scope } from '../scope'; import { applyScopeDataToEvent, mergeScopeData } from './applyScopeDataToEvent'; -import { spanToJSON } from './spanUtils'; /** * This type makes sure that we get either a CaptureContext, OR an EventHint. @@ -328,14 +327,12 @@ function normalizeEvent(event: Event | null, depth: number, maxBreadth: number): // event.spans[].data may contain circular/dangerous data so we need to normalize it if (event.spans) { normalized.spans = event.spans.map(span => { - const data = spanToJSON(span).data; - - if (data) { - // This is a bit weird, as we generally have `Span` instances here, but to be safe we do not assume so - span.setAttributes(normalize(data, depth, maxBreadth)); - } - - return span; + return { + ...span, + ...(span.data && { + data: normalize(span.data, depth, maxBreadth), + }), + }; }); } diff --git a/packages/core/test/lib/base.test.ts b/packages/core/test/lib/base.test.ts index b48d3b0e5eec..b85df791af9b 100644 --- a/packages/core/test/lib/base.test.ts +++ b/packages/core/test/lib/base.test.ts @@ -1,4 +1,4 @@ -import type { Client, Envelope, Event, Span, Transaction } from '@sentry/types'; +import type { Client, Envelope, Event, Transaction } from '@sentry/types'; import { SentryError, SyncPromise, dsnToString, logger } from '@sentry/utils'; import { @@ -897,22 +897,22 @@ describe('BaseClient', () => { { data: { _sentry_extra_metrics: { M1: { value: 1 }, M2: { value: 2 } } }, description: 'first-paint', - endTimestamp: 1591603196.637835, + timestamp: 1591603196.637835, op: 'paint', - parentSpanId: 'a3df84a60c2e4e76', - spanId: '9e15bf99fbe4bc80', - startTimestamp: 1591603196.637835, - traceId: '86f39e84263a4de99c326acab3bfe3bd', - } as unknown as Span, + parent_span_id: 'a3df84a60c2e4e76', + span_id: '9e15bf99fbe4bc80', + start_timestamp: 1591603196.637835, + trace_id: '86f39e84263a4de99c326acab3bfe3bd', + }, { description: 'first-contentful-paint', - endTimestamp: 1591603196.637835, + timestamp: 1591603196.637835, op: 'paint', - parentSpanId: 'a3df84a60c2e4e76', - spanId: 'aa554c1f506b0783', - startTimestamp: 1591603196.637835, - traceId: '86f39e84263a4de99c326acab3bfe3bd', - } as any as Span, + parent_span_id: 'a3df84a60c2e4e76', + span_id: 'aa554c1f506b0783', + start_timestamp: 1591603196.637835, + trace_id: '86f39e84263a4de99c326acab3bfe3bd', + }, ], start_timestamp: 1591603196.614865, timestamp: 1591603196.728485, @@ -1804,7 +1804,7 @@ describe('BaseClient', () => { expect(mockSend).toBeCalledTimes(1); expect(callback).toBeCalledTimes(1); - expect(callback).toBeCalledWith(errorEvent, undefined); + expect(callback).toBeCalledWith(errorEvent, 'send error'); }); it('passes the response to the hook', async () => { diff --git a/packages/core/test/lib/integrations/inboundfilters.test.ts b/packages/core/test/lib/integrations/inboundfilters.test.ts index f3c29b0398d2..72d553ce3dc2 100644 --- a/packages/core/test/lib/integrations/inboundfilters.test.ts +++ b/packages/core/test/lib/integrations/inboundfilters.test.ts @@ -1,7 +1,7 @@ import type { Event, EventProcessor } from '@sentry/types'; import type { InboundFiltersOptions } from '../../../src/integrations/inboundfilters'; -import { InboundFilters } from '../../../src/integrations/inboundfilters'; +import { inboundFiltersIntegration } from '../../../src/integrations/inboundfilters'; import { TestClient, getDefaultTestClientOptions } from '../../mocks/client'; const PUBLIC_DSN = 'https://username@domain/123'; @@ -33,8 +33,7 @@ function createInboundFiltersEventProcessor( dsn: PUBLIC_DSN, ...clientOptions, defaultIntegrations: false, - // eslint-disable-next-line deprecation/deprecation - integrations: [new InboundFilters(options)], + integrations: [inboundFiltersIntegration(options)], }), ); diff --git a/packages/core/test/lib/tracing/span.test.ts b/packages/core/test/lib/tracing/span.test.ts index 0a89b5acc5ff..9edceb86c33d 100644 --- a/packages/core/test/lib/tracing/span.test.ts +++ b/packages/core/test/lib/tracing/span.test.ts @@ -1,5 +1,6 @@ import { timestampInSeconds } from '@sentry/utils'; import { SentrySpan } from '../../../src'; +import { SPAN_STATUS_ERROR } from '../../../src/tracing/spanstatus'; import { TRACE_FLAG_NONE, TRACE_FLAG_SAMPLED, spanToJSON, spanToTraceContext } from '../../../src/utils/spanUtils'; describe('span', () => { @@ -42,7 +43,7 @@ describe('span', () => { describe('status', () => { test('setStatus', () => { const span = new SentrySpan({}); - span.setStatus('permission_denied'); + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'permission_denied' }); expect(spanToTraceContext(span).status).toBe('permission_denied'); }); }); diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index bd298fd4d5ff..37b72193d2be 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -324,7 +324,7 @@ describe('startSpan', () => { const normalizedTransactionEvents = transactionEvents.map(event => { return { ...event, - spans: event.spans?.map(span => ({ name: spanToJSON(span).description, id: span.spanContext().spanId })), + spans: event.spans?.map(span => ({ name: span.description, id: span.span_id })), sdkProcessingMetadata: { dynamicSamplingContext: event.sdkProcessingMetadata?.dynamicSamplingContext, }, @@ -360,9 +360,7 @@ describe('startSpan', () => { }, }); expect(outerTransaction?.spans).toEqual([{ name: 'inner span', id: expect.any(String) }]); - expect(outerTransaction?.tags).toEqual({ - transaction: 'outer transaction', - }); + expect(outerTransaction?.transaction).toEqual('outer transaction'); expect(outerTransaction?.sdkProcessingMetadata).toEqual({ dynamicSamplingContext: { environment: 'production', @@ -386,9 +384,7 @@ describe('startSpan', () => { }, }); expect(innerTransaction?.spans).toEqual([{ name: 'inner span 2', id: expect.any(String) }]); - expect(innerTransaction?.tags).toEqual({ - transaction: 'inner transaction', - }); + expect(innerTransaction?.transaction).toEqual('inner transaction'); expect(innerTransaction?.sdkProcessingMetadata).toEqual({ dynamicSamplingContext: { environment: 'production', @@ -607,7 +603,7 @@ describe('startSpanManual', () => { const normalizedTransactionEvents = transactionEvents.map(event => { return { ...event, - spans: event.spans?.map(span => ({ name: spanToJSON(span).description, id: span.spanContext().spanId })), + spans: event.spans?.map(span => ({ name: span.description, id: span.span_id })), sdkProcessingMetadata: { dynamicSamplingContext: event.sdkProcessingMetadata?.dynamicSamplingContext, }, @@ -643,9 +639,7 @@ describe('startSpanManual', () => { }, }); expect(outerTransaction?.spans).toEqual([{ name: 'inner span', id: expect.any(String) }]); - expect(outerTransaction?.tags).toEqual({ - transaction: 'outer transaction', - }); + expect(outerTransaction?.transaction).toEqual('outer transaction'); expect(outerTransaction?.sdkProcessingMetadata).toEqual({ dynamicSamplingContext: { environment: 'production', @@ -669,9 +663,7 @@ describe('startSpanManual', () => { }, }); expect(innerTransaction?.spans).toEqual([{ name: 'inner span 2', id: expect.any(String) }]); - expect(innerTransaction?.tags).toEqual({ - transaction: 'inner transaction', - }); + expect(innerTransaction?.transaction).toEqual('inner transaction'); expect(innerTransaction?.sdkProcessingMetadata).toEqual({ dynamicSamplingContext: { environment: 'production', @@ -818,7 +810,7 @@ describe('startInactiveSpan', () => { const normalizedTransactionEvents = transactionEvents.map(event => { return { ...event, - spans: event.spans?.map(span => ({ name: spanToJSON(span).description, id: span.spanContext().spanId })), + spans: event.spans?.map(span => ({ name: span.description, id: span.span_id })), sdkProcessingMetadata: { dynamicSamplingContext: event.sdkProcessingMetadata?.dynamicSamplingContext, }, @@ -854,9 +846,7 @@ describe('startInactiveSpan', () => { }, }); expect(outerTransaction?.spans).toEqual([{ name: 'inner span', id: expect.any(String) }]); - expect(outerTransaction?.tags).toEqual({ - transaction: 'outer transaction', - }); + expect(outerTransaction?.transaction).toEqual('outer transaction'); expect(outerTransaction?.sdkProcessingMetadata).toEqual({ dynamicSamplingContext: { environment: 'production', @@ -880,9 +870,7 @@ describe('startInactiveSpan', () => { }, }); expect(innerTransaction?.spans).toEqual([]); - expect(innerTransaction?.tags).toEqual({ - transaction: 'inner transaction', - }); + expect(innerTransaction?.transaction).toEqual('inner transaction'); expect(innerTransaction?.sdkProcessingMetadata).toEqual({ dynamicSamplingContext: { environment: 'production', @@ -948,9 +936,13 @@ describe('startInactiveSpan', () => { let span: Span | undefined; + const scope = getCurrentScope(); + scope.setTag('outer', 'foo'); + withScope(scope => { scope.setTag('scope', 1); span = startInactiveSpan({ name: 'my-span' }); + scope.setTag('scope_after_span', 2); }); withScope(scope => { @@ -964,7 +956,9 @@ describe('startInactiveSpan', () => { expect(beforeSendTransaction).toHaveBeenCalledWith( expect.objectContaining({ tags: expect.objectContaining({ + outer: 'foo', scope: 1, + scope_after_span: 2, }), }), expect.anything(), diff --git a/packages/core/test/lib/transports/base.test.ts b/packages/core/test/lib/transports/base.test.ts index 01cc8bf59c9b..1425c55c45a7 100644 --- a/packages/core/test/lib/transports/base.test.ts +++ b/packages/core/test/lib/transports/base.test.ts @@ -35,7 +35,7 @@ const transportOptions = { describe('createTransport', () => { it('flushes the buffer', async () => { - const mockBuffer: PromiseBuffer = { + const mockBuffer: PromiseBuffer = { $: [], add: jest.fn(), drain: jest.fn(), diff --git a/packages/core/test/lib/utils/spanUtils.test.ts b/packages/core/test/lib/utils/spanUtils.test.ts index 28a8e961c294..56b07405b02c 100644 --- a/packages/core/test/lib/utils/spanUtils.test.ts +++ b/packages/core/test/lib/utils/spanUtils.test.ts @@ -1,5 +1,5 @@ import { TRACEPARENT_REGEXP, timestampInSeconds } from '@sentry/utils'; -import { SentrySpan, spanToTraceHeader } from '../../../src'; +import { SPAN_STATUS_OK, SentrySpan, spanToTraceHeader } from '../../../src'; import { spanIsSampled, spanTimeInputToSeconds, spanToJSON } from '../../../src/utils/spanUtils'; describe('spanToTraceHeader', () => { @@ -72,7 +72,7 @@ describe('spanToJSON', () => { startTimestamp: 123, endTimestamp: 456, }); - span.setStatus('ok'); + span.setStatus({ code: SPAN_STATUS_OK }); expect(spanToJSON(span)).toEqual({ description: 'test name', diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 50a422f776ab..cc5f1afb4d8d 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -86,8 +86,6 @@ export { endSession, } from '@sentry/core'; -export type { SpanStatusType } from '@sentry/core'; - export { DenoClient } from './client'; export { @@ -95,8 +93,6 @@ export { init, } from './sdk'; -import { Integrations as CoreIntegrations } from '@sentry/core'; - export { denoContextIntegration } from './integrations/context'; export { globalHandlersIntegration } from './integrations/globalhandlers'; export { normalizePathsIntegration } from './integrations/normalizepaths'; @@ -108,7 +104,5 @@ import * as DenoIntegrations from './integrations'; /** @deprecated Import the integration function directly, e.g. `inboundFiltersIntegration()` instead of `new Integrations.InboundFilter(). */ export const Integrations = { - // eslint-disable-next-line deprecation/deprecation - ...CoreIntegrations, ...DenoIntegrations, }; diff --git a/packages/ember/addon/instance-initializers/sentry-performance.ts b/packages/ember/addon/instance-initializers/sentry-performance.ts index 7cd61dd222cd..552fa1df7e78 100644 --- a/packages/ember/addon/instance-initializers/sentry-performance.ts +++ b/packages/ember/addon/instance-initializers/sentry-performance.ts @@ -6,14 +6,20 @@ import type RouterService from '@ember/routing/router-service'; import { _backburner, run, scheduleOnce } from '@ember/runloop'; import type { EmberRunQueues } from '@ember/runloop/-private/types'; import { getOwnConfig, isTesting, macroCondition } from '@embroider/macros'; -import * as Sentry from '@sentry/browser'; import type { ExtendedBackburner } from '@sentry/ember/runloop'; import type { Span } from '@sentry/types'; import { GLOBAL_OBJ, browserPerformanceTimeOrigin, timestampInSeconds } from '@sentry/utils'; -import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; -import type { BrowserClient } from '..'; -import { getActiveSpan, startInactiveSpan } from '..'; +import type { BrowserClient } from '@sentry/browser'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + getActiveSpan, + getClient, + startBrowserTracingNavigationSpan, + startBrowserTracingPageLoadSpan, + startInactiveSpan, +} from '@sentry/browser'; import type { EmberRouterMain, EmberSentryConfig, GlobalConfig, OwnConfig } from '../types'; function getSentryConfig(): EmberSentryConfig { @@ -103,7 +109,7 @@ export function _instrumentEmberRouter( const browserTracingOptions = config.browserTracingOptions || config.sentry.browserTracingOptions || {}; const url = getLocationURL(location); - const client = Sentry.getClient(); + const client = getClient(); if (!client) { return; @@ -111,7 +117,7 @@ export function _instrumentEmberRouter( if (url && browserTracingOptions.instrumentPageLoad !== false) { const routeInfo = routerService.recognize(url); - activeRootSpan = Sentry.startBrowserTracingPageLoadSpan(client, { + activeRootSpan = startBrowserTracingPageLoadSpan(client, { name: `route:${routeInfo.name}`, origin: 'auto.pageload.ember', attributes: { @@ -138,7 +144,7 @@ export function _instrumentEmberRouter( const { fromRoute, toRoute } = getTransitionInformation(transition, routerService); activeRootSpan?.end(); - activeRootSpan = Sentry.startBrowserTracingNavigationSpan(client, { + activeRootSpan = startBrowserTracingNavigationSpan(client, { name: `route:${toRoute}`, origin: 'auto.navigation.ember', attributes: { @@ -416,7 +422,7 @@ export async function instrumentForPerformance(appInstance: ApplicationInstance) instrumentPageLoad: false, }); - const client = Sentry.getClient(); + const client = getClient(); const isAlreadyInitialized = macroCondition(isTesting()) ? !!client?.getIntegrationByName('BrowserTracing') : false; diff --git a/packages/ember/tests/helpers/utils.ts b/packages/ember/tests/helpers/utils.ts index 0833e80b0d8b..77ac48ebb516 100644 --- a/packages/ember/tests/helpers/utils.ts +++ b/packages/ember/tests/helpers/utils.ts @@ -1,4 +1,3 @@ -import { spanToJSON } from '@sentry/core'; import type { Event } from '@sentry/types'; const defaultAssertOptions = { @@ -68,16 +67,15 @@ export function assertSentryTransactions( // Also we ignore ui.long-task spans, as they are brittle and may or may not appear const filteredSpans = spans .filter(span => { - const op = spanToJSON(span).op; + const op = span.op; return !op?.startsWith('ui.ember.runloop.') && !op?.startsWith('ui.long-task'); }) - .map(s => { - const spanJson = spanToJSON(s); + .map(spanJson => { return `${spanJson.op} | ${spanJson.description}`; }); assert.true( - spans.some(span => spanToJSON(span).op?.startsWith('ui.ember.runloop.')), + spans.some(span => span.op?.startsWith('ui.ember.runloop.')), 'it captures runloop spans', ); assert.deepEqual(filteredSpans, options.spans, 'Has correct spans'); diff --git a/packages/feedback/src/util/handleFeedbackSubmit.ts b/packages/feedback/src/util/handleFeedbackSubmit.ts index abb3aeb1368d..a3bf57fcbfa0 100644 --- a/packages/feedback/src/util/handleFeedbackSubmit.ts +++ b/packages/feedback/src/util/handleFeedbackSubmit.ts @@ -1,5 +1,5 @@ import type { TransportMakeRequestResponse } from '@sentry/types'; -import { logger } from '@sentry/utils'; +import { logger, resolvedSyncPromise } from '@sentry/utils'; import { FEEDBACK_WIDGET_SOURCE } from '../constants'; import { DEBUG_BUILD } from '../debug-build'; @@ -15,10 +15,10 @@ export async function handleFeedbackSubmit( dialog: DialogComponent | null, feedback: FeedbackFormData, options?: SendFeedbackOptions, -): Promise { +): Promise { if (!dialog) { // Not sure when this would happen - return; + return resolvedSyncPromise({}); } const showFetchError = (): void => { @@ -39,4 +39,6 @@ export async function handleFeedbackSubmit( DEBUG_BUILD && logger.error(err); showFetchError(); } + + return resolvedSyncPromise({}); } diff --git a/packages/feedback/src/util/sendFeedbackRequest.ts b/packages/feedback/src/util/sendFeedbackRequest.ts index d8f3a880ba2c..341611d94085 100644 --- a/packages/feedback/src/util/sendFeedbackRequest.ts +++ b/packages/feedback/src/util/sendFeedbackRequest.ts @@ -1,5 +1,6 @@ import { createEventEnvelope, getClient, withScope } from '@sentry/core'; import type { FeedbackEvent, TransportMakeRequestResponse } from '@sentry/types'; +import { resolvedSyncPromise } from '@sentry/utils'; import { FEEDBACK_API_SOURCE, FEEDBACK_WIDGET_SOURCE } from '../constants'; import type { SendFeedbackData, SendFeedbackOptions } from '../types'; @@ -11,13 +12,13 @@ import { prepareFeedbackEvent } from './prepareFeedbackEvent'; export async function sendFeedbackRequest( { feedback: { message, email, name, source, url } }: SendFeedbackData, { includeReplay = true }: SendFeedbackOptions = {}, -): Promise { +): Promise { const client = getClient(); const transport = client && client.getTransport(); const dsn = client && client.getDsn(); if (!client || !transport || !dsn) { - return; + return resolvedSyncPromise({}); } const baseEvent: FeedbackEvent = { @@ -48,14 +49,14 @@ export async function sendFeedbackRequest( }); if (!feedbackEvent) { - return; + return resolvedSyncPromise({}); } client.emit('beforeSendFeedback', feedbackEvent, { includeReplay: Boolean(includeReplay) }); const envelope = createEventEnvelope(feedbackEvent, dsn, client.getOptions()._metadata, client.getOptions().tunnel); - let response: void | TransportMakeRequestResponse; + let response: TransportMakeRequestResponse; try { response = await transport.send(envelope); @@ -72,11 +73,6 @@ export async function sendFeedbackRequest( throw error; } - // TODO (v8): we can remove this guard once transport.send's type signature doesn't include void anymore - if (!response) { - return; - } - // Require valid status codes, otherwise can assume feedback was not sent successfully if (typeof response.statusCode === 'number' && (response.statusCode < 200 || response.statusCode >= 300)) { throw new Error('Unable to send Feedback'); diff --git a/packages/feedback/src/widget/createWidget.ts b/packages/feedback/src/widget/createWidget.ts index 05b52ca64725..1f6ed91bf04e 100644 --- a/packages/feedback/src/widget/createWidget.ts +++ b/packages/feedback/src/widget/createWidget.ts @@ -109,7 +109,7 @@ export function createWidget({ const result = await handleFeedbackSubmit(dialog, feedback); // Error submitting feedback - if (!result) { + if (!result || Object.keys(result).length === 0) { if (options.onSubmitError) { options.onSubmitError(); } diff --git a/packages/feedback/test/utils/mockSdk.ts b/packages/feedback/test/utils/mockSdk.ts index 0dbc30c02cb6..c6afbc018317 100644 --- a/packages/feedback/test/utils/mockSdk.ts +++ b/packages/feedback/test/utils/mockSdk.ts @@ -11,16 +11,11 @@ class MockTransport implements Transport { send: (request: Envelope) => PromiseLike; constructor() { - const send: ((request: Envelope) => PromiseLike) & { - __sentry__baseTransport__?: boolean; - } = jest.fn(async () => { + this.send = jest.fn(async () => { return { statusCode: 200, }; }); - - send.__sentry__baseTransport__ = true; - this.send = send; } async flush() { diff --git a/packages/feedback/test/widget/createWidget.test.ts b/packages/feedback/test/widget/createWidget.test.ts index c2a5f50daebc..0fba6f452084 100644 --- a/packages/feedback/test/widget/createWidget.test.ts +++ b/packages/feedback/test/widget/createWidget.test.ts @@ -130,7 +130,7 @@ describe('createWidget', () => { }); (sendFeedbackRequest as jest.Mock).mockImplementation(() => { - return Promise.resolve(true); + return Promise.resolve({ statusCode: 200 }); }); widget.actor?.el?.dispatchEvent(new Event('click')); @@ -180,7 +180,7 @@ describe('createWidget', () => { }); (sendFeedbackRequest as jest.Mock).mockImplementation(() => { - return true; + return Promise.resolve({ statusCode: 200 }); }); widget.actor?.el?.dispatchEvent(new Event('click')); diff --git a/packages/nextjs/src/common/utils/edgeWrapperUtils.ts b/packages/nextjs/src/common/utils/edgeWrapperUtils.ts index 8597228f6e83..be0c56236233 100644 --- a/packages/nextjs/src/common/utils/edgeWrapperUtils.ts +++ b/packages/nextjs/src/common/utils/edgeWrapperUtils.ts @@ -1,6 +1,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + SPAN_STATUS_OK, addTracingExtensions, captureException, continueTrace, @@ -70,7 +71,7 @@ export function withEdgeWrapping( if (handlerResult instanceof Response) { setHttpStatus(span, handlerResult.status); } else { - span.setStatus('ok'); + span.setStatus({ code: SPAN_STATUS_OK }); } } diff --git a/packages/nextjs/src/common/utils/wrapperUtils.ts b/packages/nextjs/src/common/utils/wrapperUtils.ts index 20f458bf6013..5e489fb90c09 100644 --- a/packages/nextjs/src/common/utils/wrapperUtils.ts +++ b/packages/nextjs/src/common/utils/wrapperUtils.ts @@ -2,6 +2,8 @@ import type { IncomingMessage, ServerResponse } from 'http'; import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + SPAN_STATUS_ERROR, + SPAN_STATUS_OK, captureException, continueTrace, startInactiveSpan, @@ -109,7 +111,7 @@ export function withTracedServerSideDataFetcher Pr }, }); if (requestSpan) { - requestSpan.setStatus('ok'); + requestSpan.setStatus({ code: SPAN_STATUS_OK }); setSpanOnRequest(requestSpan, req); autoEndSpanOnResponseEnd(requestSpan, res); } @@ -126,12 +128,12 @@ export function withTracedServerSideDataFetcher Pr }, }, async dataFetcherSpan => { - dataFetcherSpan?.setStatus('ok'); + dataFetcherSpan?.setStatus({ code: SPAN_STATUS_OK }); try { return await origDataFetcher.apply(this, args); } catch (e) { - dataFetcherSpan?.setStatus('internal_error'); - requestSpan?.setStatus('internal_error'); + dataFetcherSpan?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); + requestSpan?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); throw e; } finally { dataFetcherSpan?.end(); @@ -180,12 +182,12 @@ export async function callDataFetcherTraced Promis }, }, async dataFetcherSpan => { - dataFetcherSpan?.setStatus('ok'); + dataFetcherSpan?.setStatus({ code: SPAN_STATUS_OK }); try { return await origFunction(...origFunctionArgs); } catch (e) { - dataFetcherSpan?.setStatus('internal_error'); + dataFetcherSpan?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); captureException(e, { mechanism: { handled: false } }); throw e; } finally { diff --git a/packages/nextjs/src/common/withServerActionInstrumentation.ts b/packages/nextjs/src/common/withServerActionInstrumentation.ts index 0c54a4f5b665..39c26e4dfca6 100644 --- a/packages/nextjs/src/common/withServerActionInstrumentation.ts +++ b/packages/nextjs/src/common/withServerActionInstrumentation.ts @@ -1,4 +1,4 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getIsolationScope } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SPAN_STATUS_ERROR, getIsolationScope } from '@sentry/core'; import { addTracingExtensions, captureException, @@ -102,11 +102,11 @@ async function withServerActionInstrumentationImplementation { if (isNotFoundNavigationError(error)) { // We don't want to report "not-found"s - span?.setStatus('not_found'); + span?.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); } else if (isRedirectNavigationError(error)) { // Don't do anything for redirects } else { - span?.setStatus('internal_error'); + span?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); captureException(error, { mechanism: { handled: false, diff --git a/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts b/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts index d1dbecbaec63..0041d84c9838 100644 --- a/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts +++ b/packages/nextjs/src/common/wrapGenerationFunctionWithSentry.ts @@ -1,5 +1,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + SPAN_STATUS_ERROR, + SPAN_STATUS_OK, addTracingExtensions, captureException, getClient, @@ -78,12 +80,12 @@ export function wrapGenerationFunctionWithSentry a err => { if (isNotFoundNavigationError(err)) { // We don't want to report "not-found"s - span?.setStatus('not_found'); + span?.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); } else if (isRedirectNavigationError(err)) { // We don't want to report redirects - span?.setStatus('ok'); + span?.setStatus({ code: SPAN_STATUS_OK }); } else { - span?.setStatus('internal_error'); + span?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); captureException(err, { mechanism: { handled: false, diff --git a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts index 7408fa7f39f3..06a72a61ac18 100644 --- a/packages/nextjs/src/common/wrapServerComponentWithSentry.ts +++ b/packages/nextjs/src/common/wrapServerComponentWithSentry.ts @@ -1,5 +1,7 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + SPAN_STATUS_ERROR, + SPAN_STATUS_OK, addTracingExtensions, captureException, getCurrentScope, @@ -67,12 +69,12 @@ export function wrapServerComponentWithSentry any> error => { if (isNotFoundNavigationError(error)) { // We don't want to report "not-found"s - span?.setStatus('not_found'); + span?.setStatus({ code: SPAN_STATUS_ERROR, message: 'not_found' }); } else if (isRedirectNavigationError(error)) { // We don't want to report redirects - span?.setStatus('ok'); + span?.setStatus({ code: SPAN_STATUS_OK }); } else { - span?.setStatus('internal_error'); + span?.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); captureException(error, { mechanism: { handled: false, diff --git a/packages/node-experimental/src/integrations/anr/index.ts b/packages/node-experimental/src/integrations/anr/index.ts index 6e822e06b9a4..1d4cffc1e8c6 100644 --- a/packages/node-experimental/src/integrations/anr/index.ts +++ b/packages/node-experimental/src/integrations/anr/index.ts @@ -1,6 +1,7 @@ import { defineIntegration, getCurrentScope } from '@sentry/core'; import type { Contexts, Event, EventHint, IntegrationFn } from '@sentry/types'; import { logger } from '@sentry/utils'; +import * as inspector from 'inspector'; import { Worker } from 'worker_threads'; import { NODE_MAJOR, NODE_VERSION } from '../../nodeVersion'; import type { NodeClient } from '../../sdk/client'; @@ -29,11 +30,6 @@ async function getContexts(client: NodeClient): Promise { return event?.contexts || {}; } -interface InspectorApi { - open: (port: number) => void; - url: () => string | undefined; -} - const INTEGRATION_NAME = 'Anr'; const _anrIntegration = ((options: Partial = {}) => { @@ -90,8 +86,6 @@ async function _startWorker(client: NodeClient, _options: Partial void; -type InspectorSessionNodeV12 = InspectorSession & { connectToMainThread: VoidFunction }; const options: WorkerStartData = workerData; let session: Session | undefined; @@ -139,7 +138,7 @@ let debuggerPause: VoidFunction | undefined; if (options.captureStackTrace) { log('Connecting to debugger'); - const session = new InspectorSession() as InspectorSessionNodeV12; + const session = new InspectorSession(); session.connectToMainThread(); log('Connected to debugger'); diff --git a/packages/node-experimental/src/integrations/local-variables/inspector.d.ts b/packages/node-experimental/src/integrations/local-variables/inspector.d.ts deleted file mode 100644 index fca628d8405d..000000000000 --- a/packages/node-experimental/src/integrations/local-variables/inspector.d.ts +++ /dev/null @@ -1,3387 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -/* eslint-disable @typescript-eslint/no-unused-vars */ -/* eslint-disable @typescript-eslint/unified-signatures */ -/* eslint-disable @typescript-eslint/explicit-member-accessibility */ -/* eslint-disable max-lines */ -/* eslint-disable @typescript-eslint/ban-types */ -// Type definitions for inspector - -// These definitions were copied from: -// https://raw.githubusercontent.com/DefinitelyTyped/DefinitelyTyped/d37bf642ed2f3fe403e405892e2eb4240a191bb0/types/node/inspector.d.ts - -/** - * The `inspector` module provides an API for interacting with the V8 inspector. - * - * It can be accessed using: - * - * ```js - * const inspector = require('inspector'); - * ``` - * @see [source](https://github.com/nodejs/node/blob/v18.0.0/lib/inspector.js) - */ -declare module 'inspector' { - import EventEmitter = require('node:events'); - interface InspectorNotification { - method: string; - params: T; - } - namespace Schema { - /** - * Description of the protocol domain. - */ - interface Domain { - /** - * Domain name. - */ - name: string; - /** - * Domain version. - */ - version: string; - } - interface GetDomainsReturnType { - /** - * List of supported domains. - */ - domains: Domain[]; - } - } - namespace Runtime { - /** - * Unique script identifier. - */ - type ScriptId = string; - /** - * Unique object identifier. - */ - type RemoteObjectId = string; - /** - * Primitive value which cannot be JSON-stringified. - */ - type UnserializableValue = string; - /** - * Mirror object referencing original JavaScript object. - */ - interface RemoteObject { - /** - * Object type. - */ - type: string; - /** - * Object subtype hint. Specified for object type values only. - */ - subtype?: string | undefined; - /** - * Object class (constructor) name. Specified for object type values only. - */ - className?: string | undefined; - /** - * Remote object value in case of primitive values or JSON values (if it was requested). - */ - value?: any; - /** - * Primitive value which can not be JSON-stringified does not have value, but gets this property. - */ - unserializableValue?: UnserializableValue | undefined; - /** - * String representation of the object. - */ - description?: string | undefined; - /** - * Unique object identifier (for non-primitive values). - */ - objectId?: RemoteObjectId | undefined; - /** - * Preview containing abbreviated property values. Specified for object type values only. - * @experimental - */ - preview?: ObjectPreview | undefined; - /** - * @experimental - */ - customPreview?: CustomPreview | undefined; - } - /** - * @experimental - */ - interface CustomPreview { - header: string; - hasBody: boolean; - formatterObjectId: RemoteObjectId; - bindRemoteObjectFunctionId: RemoteObjectId; - configObjectId?: RemoteObjectId | undefined; - } - /** - * Object containing abbreviated remote object value. - * @experimental - */ - interface ObjectPreview { - /** - * Object type. - */ - type: string; - /** - * Object subtype hint. Specified for object type values only. - */ - subtype?: string | undefined; - /** - * String representation of the object. - */ - description?: string | undefined; - /** - * True iff some of the properties or entries of the original object did not fit. - */ - overflow: boolean; - /** - * List of the properties. - */ - properties: PropertyPreview[]; - /** - * List of the entries. Specified for map and set subtype values only. - */ - entries?: EntryPreview[] | undefined; - } - /** - * @experimental - */ - interface PropertyPreview { - /** - * Property name. - */ - name: string; - /** - * Object type. Accessor means that the property itself is an accessor property. - */ - type: string; - /** - * User-friendly property value string. - */ - value?: string | undefined; - /** - * Nested value preview. - */ - valuePreview?: ObjectPreview | undefined; - /** - * Object subtype hint. Specified for object type values only. - */ - subtype?: string | undefined; - } - /** - * @experimental - */ - interface EntryPreview { - /** - * Preview of the key. Specified for map-like collection entries. - */ - key?: ObjectPreview | undefined; - /** - * Preview of the value. - */ - value: ObjectPreview; - } - /** - * Object property descriptor. - */ - interface PropertyDescriptor { - /** - * Property name or symbol description. - */ - name: string; - /** - * The value associated with the property. - */ - value?: RemoteObject | undefined; - /** - * True if the value associated with the property may be changed (data descriptors only). - */ - writable?: boolean | undefined; - /** - * A function which serves as a getter for the property, or undefined if there is no getter (accessor descriptors only). - */ - get?: RemoteObject | undefined; - /** - * A function which serves as a setter for the property, or undefined if there is no setter (accessor descriptors only). - */ - set?: RemoteObject | undefined; - /** - * True if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object. - */ - configurable: boolean; - /** - * True if this property shows up during enumeration of the properties on the corresponding object. - */ - enumerable: boolean; - /** - * True if the result was thrown during the evaluation. - */ - wasThrown?: boolean | undefined; - /** - * True if the property is owned for the object. - */ - isOwn?: boolean | undefined; - /** - * Property symbol object, if the property is of the symbol type. - */ - symbol?: RemoteObject | undefined; - } - /** - * Object internal property descriptor. This property isn't normally visible in JavaScript code. - */ - interface InternalPropertyDescriptor { - /** - * Conventional property name. - */ - name: string; - /** - * The value associated with the property. - */ - value?: RemoteObject | undefined; - } - /** - * Represents function call argument. Either remote object id objectId, primitive value, unserializable primitive value or neither of (for undefined) them should be specified. - */ - interface CallArgument { - /** - * Primitive value or serializable javascript object. - */ - value?: any; - /** - * Primitive value which can not be JSON-stringified. - */ - unserializableValue?: UnserializableValue | undefined; - /** - * Remote object handle. - */ - objectId?: RemoteObjectId | undefined; - } - /** - * Id of an execution context. - */ - type ExecutionContextId = number; - /** - * Description of an isolated world. - */ - interface ExecutionContextDescription { - /** - * Unique id of the execution context. It can be used to specify in which execution context script evaluation should be performed. - */ - id: ExecutionContextId; - /** - * Execution context origin. - */ - origin: string; - /** - * Human readable name describing given context. - */ - name: string; - /** - * Embedder-specific auxiliary data. - */ - auxData?: {} | undefined; - } - /** - * Detailed information about exception (or error) that was thrown during script compilation or execution. - */ - interface ExceptionDetails { - /** - * Exception id. - */ - exceptionId: number; - /** - * Exception text, which should be used together with exception object when available. - */ - text: string; - /** - * Line number of the exception location (0-based). - */ - lineNumber: number; - /** - * Column number of the exception location (0-based). - */ - columnNumber: number; - /** - * Script ID of the exception location. - */ - scriptId?: ScriptId | undefined; - /** - * URL of the exception location, to be used when the script was not reported. - */ - url?: string | undefined; - /** - * JavaScript stack trace if available. - */ - stackTrace?: StackTrace | undefined; - /** - * Exception object if available. - */ - exception?: RemoteObject | undefined; - /** - * Identifier of the context where exception happened. - */ - executionContextId?: ExecutionContextId | undefined; - } - /** - * Number of milliseconds since epoch. - */ - type Timestamp = number; - /** - * Stack entry for runtime errors and assertions. - */ - interface CallFrame { - /** - * JavaScript function name. - */ - functionName: string; - /** - * JavaScript script id. - */ - scriptId: ScriptId; - /** - * JavaScript script name or url. - */ - url: string; - /** - * JavaScript script line number (0-based). - */ - lineNumber: number; - /** - * JavaScript script column number (0-based). - */ - columnNumber: number; - } - /** - * Call frames for assertions or error messages. - */ - interface StackTrace { - /** - * String label of this stack trace. For async traces this may be a name of the function that initiated the async call. - */ - description?: string | undefined; - /** - * JavaScript function name. - */ - callFrames: CallFrame[]; - /** - * Asynchronous JavaScript stack trace that preceded this stack, if available. - */ - parent?: StackTrace | undefined; - /** - * Asynchronous JavaScript stack trace that preceded this stack, if available. - * @experimental - */ - parentId?: StackTraceId | undefined; - } - /** - * Unique identifier of current debugger. - * @experimental - */ - type UniqueDebuggerId = string; - /** - * If debuggerId is set stack trace comes from another debugger and can be resolved there. This allows to track cross-debugger calls. See Runtime.StackTrace and Debugger.paused for usages. - * @experimental - */ - interface StackTraceId { - id: string; - debuggerId?: UniqueDebuggerId | undefined; - } - interface EvaluateParameterType { - /** - * Expression to evaluate. - */ - expression: string; - /** - * Symbolic group name that can be used to release multiple objects. - */ - objectGroup?: string | undefined; - /** - * Determines whether Command Line API should be available during the evaluation. - */ - includeCommandLineAPI?: boolean | undefined; - /** - * In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state. - */ - silent?: boolean | undefined; - /** - * Specifies in which execution context to perform evaluation. If the parameter is omitted the evaluation will be performed in the context of the inspected page. - */ - contextId?: ExecutionContextId | undefined; - /** - * Whether the result is expected to be a JSON object that should be sent by value. - */ - returnByValue?: boolean | undefined; - /** - * Whether preview should be generated for the result. - * @experimental - */ - generatePreview?: boolean | undefined; - /** - * Whether execution should be treated as initiated by user in the UI. - */ - userGesture?: boolean | undefined; - /** - * Whether execution should await for resulting value and return once awaited promise is resolved. - */ - awaitPromise?: boolean | undefined; - } - interface AwaitPromiseParameterType { - /** - * Identifier of the promise. - */ - promiseObjectId: RemoteObjectId; - /** - * Whether the result is expected to be a JSON object that should be sent by value. - */ - returnByValue?: boolean | undefined; - /** - * Whether preview should be generated for the result. - */ - generatePreview?: boolean | undefined; - } - interface CallFunctionOnParameterType { - /** - * Declaration of the function to call. - */ - functionDeclaration: string; - /** - * Identifier of the object to call function on. Either objectId or executionContextId should be specified. - */ - objectId?: RemoteObjectId | undefined; - /** - * Call arguments. All call arguments must belong to the same JavaScript world as the target object. - */ - arguments?: CallArgument[] | undefined; - /** - * In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state. - */ - silent?: boolean | undefined; - /** - * Whether the result is expected to be a JSON object which should be sent by value. - */ - returnByValue?: boolean | undefined; - /** - * Whether preview should be generated for the result. - * @experimental - */ - generatePreview?: boolean | undefined; - /** - * Whether execution should be treated as initiated by user in the UI. - */ - userGesture?: boolean | undefined; - /** - * Whether execution should await for resulting value and return once awaited promise is resolved. - */ - awaitPromise?: boolean | undefined; - /** - * Specifies execution context which global object will be used to call function on. Either executionContextId or objectId should be specified. - */ - executionContextId?: ExecutionContextId | undefined; - /** - * Symbolic group name that can be used to release multiple objects. If objectGroup is not specified and objectId is, objectGroup will be inherited from object. - */ - objectGroup?: string | undefined; - } - interface GetPropertiesParameterType { - /** - * Identifier of the object to return properties for. - */ - objectId: RemoteObjectId; - /** - * If true, returns properties belonging only to the element itself, not to its prototype chain. - */ - ownProperties?: boolean | undefined; - /** - * If true, returns accessor properties (with getter/setter) only; internal properties are not returned either. - * @experimental - */ - accessorPropertiesOnly?: boolean | undefined; - /** - * Whether preview should be generated for the results. - * @experimental - */ - generatePreview?: boolean | undefined; - } - interface ReleaseObjectParameterType { - /** - * Identifier of the object to release. - */ - objectId: RemoteObjectId; - } - interface ReleaseObjectGroupParameterType { - /** - * Symbolic object group name. - */ - objectGroup: string; - } - interface SetCustomObjectFormatterEnabledParameterType { - enabled: boolean; - } - interface CompileScriptParameterType { - /** - * Expression to compile. - */ - expression: string; - /** - * Source url to be set for the script. - */ - sourceURL: string; - /** - * Specifies whether the compiled script should be persisted. - */ - persistScript: boolean; - /** - * Specifies in which execution context to perform script run. If the parameter is omitted the evaluation will be performed in the context of the inspected page. - */ - executionContextId?: ExecutionContextId | undefined; - } - interface RunScriptParameterType { - /** - * Id of the script to run. - */ - scriptId: ScriptId; - /** - * Specifies in which execution context to perform script run. If the parameter is omitted the evaluation will be performed in the context of the inspected page. - */ - executionContextId?: ExecutionContextId | undefined; - /** - * Symbolic group name that can be used to release multiple objects. - */ - objectGroup?: string | undefined; - /** - * In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state. - */ - silent?: boolean | undefined; - /** - * Determines whether Command Line API should be available during the evaluation. - */ - includeCommandLineAPI?: boolean | undefined; - /** - * Whether the result is expected to be a JSON object which should be sent by value. - */ - returnByValue?: boolean | undefined; - /** - * Whether preview should be generated for the result. - */ - generatePreview?: boolean | undefined; - /** - * Whether execution should await for resulting value and return once awaited promise is resolved. - */ - awaitPromise?: boolean | undefined; - } - interface QueryObjectsParameterType { - /** - * Identifier of the prototype to return objects for. - */ - prototypeObjectId: RemoteObjectId; - } - interface GlobalLexicalScopeNamesParameterType { - /** - * Specifies in which execution context to lookup global scope variables. - */ - executionContextId?: ExecutionContextId | undefined; - } - interface EvaluateReturnType { - /** - * Evaluation result. - */ - result: RemoteObject; - /** - * Exception details. - */ - exceptionDetails?: ExceptionDetails | undefined; - } - interface AwaitPromiseReturnType { - /** - * Promise result. Will contain rejected value if promise was rejected. - */ - result: RemoteObject; - /** - * Exception details if stack strace is available. - */ - exceptionDetails?: ExceptionDetails | undefined; - } - interface CallFunctionOnReturnType { - /** - * Call result. - */ - result: RemoteObject; - /** - * Exception details. - */ - exceptionDetails?: ExceptionDetails | undefined; - } - interface GetPropertiesReturnType { - /** - * Object properties. - */ - result: PropertyDescriptor[]; - /** - * Internal object properties (only of the element itself). - */ - internalProperties?: InternalPropertyDescriptor[] | undefined; - /** - * Exception details. - */ - exceptionDetails?: ExceptionDetails | undefined; - } - interface CompileScriptReturnType { - /** - * Id of the script. - */ - scriptId?: ScriptId | undefined; - /** - * Exception details. - */ - exceptionDetails?: ExceptionDetails | undefined; - } - interface RunScriptReturnType { - /** - * Run result. - */ - result: RemoteObject; - /** - * Exception details. - */ - exceptionDetails?: ExceptionDetails | undefined; - } - interface QueryObjectsReturnType { - /** - * Array with objects. - */ - objects: RemoteObject; - } - interface GlobalLexicalScopeNamesReturnType { - names: string[]; - } - interface ExecutionContextCreatedEventDataType { - /** - * A newly created execution context. - */ - context: ExecutionContextDescription; - } - interface ExecutionContextDestroyedEventDataType { - /** - * Id of the destroyed context - */ - executionContextId: ExecutionContextId; - } - interface ExceptionThrownEventDataType { - /** - * Timestamp of the exception. - */ - timestamp: Timestamp; - exceptionDetails: ExceptionDetails; - } - interface ExceptionRevokedEventDataType { - /** - * Reason describing why exception was revoked. - */ - reason: string; - /** - * The id of revoked exception, as reported in exceptionThrown. - */ - exceptionId: number; - } - interface ConsoleAPICalledEventDataType { - /** - * Type of the call. - */ - type: string; - /** - * Call arguments. - */ - args: RemoteObject[]; - /** - * Identifier of the context where the call was made. - */ - executionContextId: ExecutionContextId; - /** - * Call timestamp. - */ - timestamp: Timestamp; - /** - * Stack trace captured when the call was made. - */ - stackTrace?: StackTrace | undefined; - /** - * Console context descriptor for calls on non-default console context (not console.*): 'anonymous#unique-logger-id' for call on unnamed context, 'name#unique-logger-id' for call on named context. - * @experimental - */ - context?: string | undefined; - } - interface InspectRequestedEventDataType { - object: RemoteObject; - hints: {}; - } - } - namespace Debugger { - /** - * Breakpoint identifier. - */ - type BreakpointId = string; - /** - * Call frame identifier. - */ - type CallFrameId = string; - /** - * Location in the source code. - */ - interface Location { - /** - * Script identifier as reported in the Debugger.scriptParsed. - */ - scriptId: Runtime.ScriptId; - /** - * Line number in the script (0-based). - */ - lineNumber: number; - /** - * Column number in the script (0-based). - */ - columnNumber?: number | undefined; - } - /** - * Location in the source code. - * @experimental - */ - interface ScriptPosition { - lineNumber: number; - columnNumber: number; - } - /** - * JavaScript call frame. Array of call frames form the call stack. - */ - interface CallFrame { - /** - * Call frame identifier. This identifier is only valid while the virtual machine is paused. - */ - callFrameId: CallFrameId; - /** - * Name of the JavaScript function called on this call frame. - */ - functionName: string; - /** - * Location in the source code. - */ - functionLocation?: Location | undefined; - /** - * Location in the source code. - */ - location: Location; - /** - * JavaScript script name or url. - */ - url: string; - /** - * Scope chain for this call frame. - */ - scopeChain: Scope[]; - /** - * this object for this call frame. - */ - this: Runtime.RemoteObject; - /** - * The value being returned, if the function is at return point. - */ - returnValue?: Runtime.RemoteObject | undefined; - } - /** - * Scope description. - */ - interface Scope { - /** - * Scope type. - */ - type: string; - /** - * Object representing the scope. For global and with scopes it represents the actual object; for the rest of the scopes, it is artificial transient object enumerating scope variables as its properties. - */ - object: Runtime.RemoteObject; - name?: string | undefined; - /** - * Location in the source code where scope starts - */ - startLocation?: Location | undefined; - /** - * Location in the source code where scope ends - */ - endLocation?: Location | undefined; - } - /** - * Search match for resource. - */ - interface SearchMatch { - /** - * Line number in resource content. - */ - lineNumber: number; - /** - * Line with match content. - */ - lineContent: string; - } - interface BreakLocation { - /** - * Script identifier as reported in the Debugger.scriptParsed. - */ - scriptId: Runtime.ScriptId; - /** - * Line number in the script (0-based). - */ - lineNumber: number; - /** - * Column number in the script (0-based). - */ - columnNumber?: number | undefined; - type?: string | undefined; - } - interface SetBreakpointsActiveParameterType { - /** - * New value for breakpoints active state. - */ - active: boolean; - } - interface SetSkipAllPausesParameterType { - /** - * New value for skip pauses state. - */ - skip: boolean; - } - interface SetBreakpointByUrlParameterType { - /** - * Line number to set breakpoint at. - */ - lineNumber: number; - /** - * URL of the resources to set breakpoint on. - */ - url?: string | undefined; - /** - * Regex pattern for the URLs of the resources to set breakpoints on. Either url or urlRegex must be specified. - */ - urlRegex?: string | undefined; - /** - * Script hash of the resources to set breakpoint on. - */ - scriptHash?: string | undefined; - /** - * Offset in the line to set breakpoint at. - */ - columnNumber?: number | undefined; - /** - * Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true. - */ - condition?: string | undefined; - } - interface SetBreakpointParameterType { - /** - * Location to set breakpoint in. - */ - location: Location; - /** - * Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true. - */ - condition?: string | undefined; - } - interface RemoveBreakpointParameterType { - breakpointId: BreakpointId; - } - interface GetPossibleBreakpointsParameterType { - /** - * Start of range to search possible breakpoint locations in. - */ - start: Location; - /** - * End of range to search possible breakpoint locations in (excluding). When not specified, end of scripts is used as end of range. - */ - end?: Location | undefined; - /** - * Only consider locations which are in the same (non-nested) function as start. - */ - restrictToFunction?: boolean | undefined; - } - interface ContinueToLocationParameterType { - /** - * Location to continue to. - */ - location: Location; - targetCallFrames?: string | undefined; - } - interface PauseOnAsyncCallParameterType { - /** - * Debugger will pause when async call with given stack trace is started. - */ - parentStackTraceId: Runtime.StackTraceId; - } - interface StepIntoParameterType { - /** - * Debugger will issue additional Debugger.paused notification if any async task is scheduled before next pause. - * @experimental - */ - breakOnAsyncCall?: boolean | undefined; - } - interface GetStackTraceParameterType { - stackTraceId: Runtime.StackTraceId; - } - interface SearchInContentParameterType { - /** - * Id of the script to search in. - */ - scriptId: Runtime.ScriptId; - /** - * String to search for. - */ - query: string; - /** - * If true, search is case sensitive. - */ - caseSensitive?: boolean | undefined; - /** - * If true, treats string parameter as regex. - */ - isRegex?: boolean | undefined; - } - interface SetScriptSourceParameterType { - /** - * Id of the script to edit. - */ - scriptId: Runtime.ScriptId; - /** - * New content of the script. - */ - scriptSource: string; - /** - * If true the change will not actually be applied. Dry run may be used to get result description without actually modifying the code. - */ - dryRun?: boolean | undefined; - } - interface RestartFrameParameterType { - /** - * Call frame identifier to evaluate on. - */ - callFrameId: CallFrameId; - } - interface GetScriptSourceParameterType { - /** - * Id of the script to get source for. - */ - scriptId: Runtime.ScriptId; - } - interface SetPauseOnExceptionsParameterType { - /** - * Pause on exceptions mode. - */ - state: string; - } - interface EvaluateOnCallFrameParameterType { - /** - * Call frame identifier to evaluate on. - */ - callFrameId: CallFrameId; - /** - * Expression to evaluate. - */ - expression: string; - /** - * String object group name to put result into (allows rapid releasing resulting object handles using releaseObjectGroup). - */ - objectGroup?: string | undefined; - /** - * Specifies whether command line API should be available to the evaluated expression, defaults to false. - */ - includeCommandLineAPI?: boolean | undefined; - /** - * In silent mode exceptions thrown during evaluation are not reported and do not pause execution. Overrides setPauseOnException state. - */ - silent?: boolean | undefined; - /** - * Whether the result is expected to be a JSON object that should be sent by value. - */ - returnByValue?: boolean | undefined; - /** - * Whether preview should be generated for the result. - * @experimental - */ - generatePreview?: boolean | undefined; - /** - * Whether to throw an exception if side effect cannot be ruled out during evaluation. - */ - throwOnSideEffect?: boolean | undefined; - } - interface SetVariableValueParameterType { - /** - * 0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch' scope types are allowed. Other scopes could be manipulated manually. - */ - scopeNumber: number; - /** - * Variable name. - */ - variableName: string; - /** - * New variable value. - */ - newValue: Runtime.CallArgument; - /** - * Id of callframe that holds variable. - */ - callFrameId: CallFrameId; - } - interface SetReturnValueParameterType { - /** - * New return value. - */ - newValue: Runtime.CallArgument; - } - interface SetAsyncCallStackDepthParameterType { - /** - * Maximum depth of async call stacks. Setting to 0 will effectively disable collecting async call stacks (default). - */ - maxDepth: number; - } - interface SetBlackboxPatternsParameterType { - /** - * Array of regexps that will be used to check script url for blackbox state. - */ - patterns: string[]; - } - interface SetBlackboxedRangesParameterType { - /** - * Id of the script. - */ - scriptId: Runtime.ScriptId; - positions: ScriptPosition[]; - } - interface EnableReturnType { - /** - * Unique identifier of the debugger. - * @experimental - */ - debuggerId: Runtime.UniqueDebuggerId; - } - interface SetBreakpointByUrlReturnType { - /** - * Id of the created breakpoint for further reference. - */ - breakpointId: BreakpointId; - /** - * List of the locations this breakpoint resolved into upon addition. - */ - locations: Location[]; - } - interface SetBreakpointReturnType { - /** - * Id of the created breakpoint for further reference. - */ - breakpointId: BreakpointId; - /** - * Location this breakpoint resolved into. - */ - actualLocation: Location; - } - interface GetPossibleBreakpointsReturnType { - /** - * List of the possible breakpoint locations. - */ - locations: BreakLocation[]; - } - interface GetStackTraceReturnType { - stackTrace: Runtime.StackTrace; - } - interface SearchInContentReturnType { - /** - * List of search matches. - */ - result: SearchMatch[]; - } - interface SetScriptSourceReturnType { - /** - * New stack trace in case editing has happened while VM was stopped. - */ - callFrames?: CallFrame[] | undefined; - /** - * Whether current call stack was modified after applying the changes. - */ - stackChanged?: boolean | undefined; - /** - * Async stack trace, if any. - */ - asyncStackTrace?: Runtime.StackTrace | undefined; - /** - * Async stack trace, if any. - * @experimental - */ - asyncStackTraceId?: Runtime.StackTraceId | undefined; - /** - * Exception details if any. - */ - exceptionDetails?: Runtime.ExceptionDetails | undefined; - } - interface RestartFrameReturnType { - /** - * New stack trace. - */ - callFrames: CallFrame[]; - /** - * Async stack trace, if any. - */ - asyncStackTrace?: Runtime.StackTrace | undefined; - /** - * Async stack trace, if any. - * @experimental - */ - asyncStackTraceId?: Runtime.StackTraceId | undefined; - } - interface GetScriptSourceReturnType { - /** - * Script source. - */ - scriptSource: string; - } - interface EvaluateOnCallFrameReturnType { - /** - * Object wrapper for the evaluation result. - */ - result: Runtime.RemoteObject; - /** - * Exception details. - */ - exceptionDetails?: Runtime.ExceptionDetails | undefined; - } - interface ScriptParsedEventDataType { - /** - * Identifier of the script parsed. - */ - scriptId: Runtime.ScriptId; - /** - * URL or name of the script parsed (if any). - */ - url: string; - /** - * Line offset of the script within the resource with given URL (for script tags). - */ - startLine: number; - /** - * Column offset of the script within the resource with given URL. - */ - startColumn: number; - /** - * Last line of the script. - */ - endLine: number; - /** - * Length of the last line of the script. - */ - endColumn: number; - /** - * Specifies script creation context. - */ - executionContextId: Runtime.ExecutionContextId; - /** - * Content hash of the script. - */ - hash: string; - /** - * Embedder-specific auxiliary data. - */ - executionContextAuxData?: {} | undefined; - /** - * True, if this script is generated as a result of the live edit operation. - * @experimental - */ - isLiveEdit?: boolean | undefined; - /** - * URL of source map associated with script (if any). - */ - sourceMapURL?: string | undefined; - /** - * True, if this script has sourceURL. - */ - hasSourceURL?: boolean | undefined; - /** - * True, if this script is ES6 module. - */ - isModule?: boolean | undefined; - /** - * This script length. - */ - length?: number | undefined; - /** - * JavaScript top stack frame of where the script parsed event was triggered if available. - * @experimental - */ - stackTrace?: Runtime.StackTrace | undefined; - } - interface ScriptFailedToParseEventDataType { - /** - * Identifier of the script parsed. - */ - scriptId: Runtime.ScriptId; - /** - * URL or name of the script parsed (if any). - */ - url: string; - /** - * Line offset of the script within the resource with given URL (for script tags). - */ - startLine: number; - /** - * Column offset of the script within the resource with given URL. - */ - startColumn: number; - /** - * Last line of the script. - */ - endLine: number; - /** - * Length of the last line of the script. - */ - endColumn: number; - /** - * Specifies script creation context. - */ - executionContextId: Runtime.ExecutionContextId; - /** - * Content hash of the script. - */ - hash: string; - /** - * Embedder-specific auxiliary data. - */ - executionContextAuxData?: {} | undefined; - /** - * URL of source map associated with script (if any). - */ - sourceMapURL?: string | undefined; - /** - * True, if this script has sourceURL. - */ - hasSourceURL?: boolean | undefined; - /** - * True, if this script is ES6 module. - */ - isModule?: boolean | undefined; - /** - * This script length. - */ - length?: number | undefined; - /** - * JavaScript top stack frame of where the script parsed event was triggered if available. - * @experimental - */ - stackTrace?: Runtime.StackTrace | undefined; - } - interface BreakpointResolvedEventDataType { - /** - * Breakpoint unique identifier. - */ - breakpointId: BreakpointId; - /** - * Actual breakpoint location. - */ - location: Location; - } - interface PausedEventDataType { - /** - * Call stack the virtual machine stopped on. - */ - callFrames: CallFrame[]; - /** - * Pause reason. - */ - reason: string; - /** - * Object containing break-specific auxiliary properties. - */ - data?: {} | undefined; - /** - * Hit breakpoints IDs - */ - hitBreakpoints?: string[] | undefined; - /** - * Async stack trace, if any. - */ - asyncStackTrace?: Runtime.StackTrace | undefined; - /** - * Async stack trace, if any. - * @experimental - */ - asyncStackTraceId?: Runtime.StackTraceId | undefined; - /** - * Just scheduled async call will have this stack trace as parent stack during async execution. This field is available only after Debugger.stepInto call with breakOnAsynCall flag. - * @experimental - */ - asyncCallStackTraceId?: Runtime.StackTraceId | undefined; - } - } - namespace Console { - /** - * Console message. - */ - interface ConsoleMessage { - /** - * Message source. - */ - source: string; - /** - * Message severity. - */ - level: string; - /** - * Message text. - */ - text: string; - /** - * URL of the message origin. - */ - url?: string | undefined; - /** - * Line number in the resource that generated this message (1-based). - */ - line?: number | undefined; - /** - * Column number in the resource that generated this message (1-based). - */ - column?: number | undefined; - } - interface MessageAddedEventDataType { - /** - * Console message that has been added. - */ - message: ConsoleMessage; - } - } - namespace Profiler { - /** - * Profile node. Holds callsite information, execution statistics and child nodes. - */ - interface ProfileNode { - /** - * Unique id of the node. - */ - id: number; - /** - * Function location. - */ - callFrame: Runtime.CallFrame; - /** - * Number of samples where this node was on top of the call stack. - */ - hitCount?: number | undefined; - /** - * Child node ids. - */ - children?: number[] | undefined; - /** - * The reason of being not optimized. The function may be deoptimized or marked as don't optimize. - */ - deoptReason?: string | undefined; - /** - * An array of source position ticks. - */ - positionTicks?: PositionTickInfo[] | undefined; - } - /** - * Profile. - */ - interface Profile { - /** - * The list of profile nodes. First item is the root node. - */ - nodes: ProfileNode[]; - /** - * Profiling start timestamp in microseconds. - */ - startTime: number; - /** - * Profiling end timestamp in microseconds. - */ - endTime: number; - /** - * Ids of samples top nodes. - */ - samples?: number[] | undefined; - /** - * Time intervals between adjacent samples in microseconds. The first delta is relative to the profile startTime. - */ - timeDeltas?: number[] | undefined; - } - /** - * Specifies a number of samples attributed to a certain source position. - */ - interface PositionTickInfo { - /** - * Source line number (1-based). - */ - line: number; - /** - * Number of samples attributed to the source line. - */ - ticks: number; - } - /** - * Coverage data for a source range. - */ - interface CoverageRange { - /** - * JavaScript script source offset for the range start. - */ - startOffset: number; - /** - * JavaScript script source offset for the range end. - */ - endOffset: number; - /** - * Collected execution count of the source range. - */ - count: number; - } - /** - * Coverage data for a JavaScript function. - */ - interface FunctionCoverage { - /** - * JavaScript function name. - */ - functionName: string; - /** - * Source ranges inside the function with coverage data. - */ - ranges: CoverageRange[]; - /** - * Whether coverage data for this function has block granularity. - */ - isBlockCoverage: boolean; - } - /** - * Coverage data for a JavaScript script. - */ - interface ScriptCoverage { - /** - * JavaScript script id. - */ - scriptId: Runtime.ScriptId; - /** - * JavaScript script name or url. - */ - url: string; - /** - * Functions contained in the script that has coverage data. - */ - functions: FunctionCoverage[]; - } - /** - * Describes a type collected during runtime. - * @experimental - */ - interface TypeObject { - /** - * Name of a type collected with type profiling. - */ - name: string; - } - /** - * Source offset and types for a parameter or return value. - * @experimental - */ - interface TypeProfileEntry { - /** - * Source offset of the parameter or end of function for return values. - */ - offset: number; - /** - * The types for this parameter or return value. - */ - types: TypeObject[]; - } - /** - * Type profile data collected during runtime for a JavaScript script. - * @experimental - */ - interface ScriptTypeProfile { - /** - * JavaScript script id. - */ - scriptId: Runtime.ScriptId; - /** - * JavaScript script name or url. - */ - url: string; - /** - * Type profile entries for parameters and return values of the functions in the script. - */ - entries: TypeProfileEntry[]; - } - interface SetSamplingIntervalParameterType { - /** - * New sampling interval in microseconds. - */ - interval: number; - } - interface StartPreciseCoverageParameterType { - /** - * Collect accurate call counts beyond simple 'covered' or 'not covered'. - */ - callCount?: boolean | undefined; - /** - * Collect block-based coverage. - */ - detailed?: boolean | undefined; - } - interface StopReturnType { - /** - * Recorded profile. - */ - profile: Profile; - } - interface TakePreciseCoverageReturnType { - /** - * Coverage data for the current isolate. - */ - result: ScriptCoverage[]; - } - interface GetBestEffortCoverageReturnType { - /** - * Coverage data for the current isolate. - */ - result: ScriptCoverage[]; - } - interface TakeTypeProfileReturnType { - /** - * Type profile for all scripts since startTypeProfile() was turned on. - */ - result: ScriptTypeProfile[]; - } - interface ConsoleProfileStartedEventDataType { - id: string; - /** - * Location of console.profile(). - */ - location: Debugger.Location; - /** - * Profile title passed as an argument to console.profile(). - */ - title?: string | undefined; - } - interface ConsoleProfileFinishedEventDataType { - id: string; - /** - * Location of console.profileEnd(). - */ - location: Debugger.Location; - profile: Profile; - /** - * Profile title passed as an argument to console.profile(). - */ - title?: string | undefined; - } - } - namespace HeapProfiler { - /** - * Heap snapshot object id. - */ - type HeapSnapshotObjectId = string; - /** - * Sampling Heap Profile node. Holds callsite information, allocation statistics and child nodes. - */ - interface SamplingHeapProfileNode { - /** - * Function location. - */ - callFrame: Runtime.CallFrame; - /** - * Allocations size in bytes for the node excluding children. - */ - selfSize: number; - /** - * Child nodes. - */ - children: SamplingHeapProfileNode[]; - } - /** - * Profile. - */ - interface SamplingHeapProfile { - head: SamplingHeapProfileNode; - } - interface StartTrackingHeapObjectsParameterType { - trackAllocations?: boolean | undefined; - } - interface StopTrackingHeapObjectsParameterType { - /** - * If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken when the tracking is stopped. - */ - reportProgress?: boolean | undefined; - } - interface TakeHeapSnapshotParameterType { - /** - * If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken. - */ - reportProgress?: boolean | undefined; - } - interface GetObjectByHeapObjectIdParameterType { - objectId: HeapSnapshotObjectId; - /** - * Symbolic group name that can be used to release multiple objects. - */ - objectGroup?: string | undefined; - } - interface AddInspectedHeapObjectParameterType { - /** - * Heap snapshot object id to be accessible by means of $x command line API. - */ - heapObjectId: HeapSnapshotObjectId; - } - interface GetHeapObjectIdParameterType { - /** - * Identifier of the object to get heap object id for. - */ - objectId: Runtime.RemoteObjectId; - } - interface StartSamplingParameterType { - /** - * Average sample interval in bytes. Poisson distribution is used for the intervals. The default value is 32768 bytes. - */ - samplingInterval?: number | undefined; - } - interface GetObjectByHeapObjectIdReturnType { - /** - * Evaluation result. - */ - result: Runtime.RemoteObject; - } - interface GetHeapObjectIdReturnType { - /** - * Id of the heap snapshot object corresponding to the passed remote object id. - */ - heapSnapshotObjectId: HeapSnapshotObjectId; - } - interface StopSamplingReturnType { - /** - * Recorded sampling heap profile. - */ - profile: SamplingHeapProfile; - } - interface GetSamplingProfileReturnType { - /** - * Return the sampling profile being collected. - */ - profile: SamplingHeapProfile; - } - interface AddHeapSnapshotChunkEventDataType { - chunk: string; - } - interface ReportHeapSnapshotProgressEventDataType { - done: number; - total: number; - finished?: boolean | undefined; - } - interface LastSeenObjectIdEventDataType { - lastSeenObjectId: number; - timestamp: number; - } - interface HeapStatsUpdateEventDataType { - /** - * An array of triplets. Each triplet describes a fragment. The first integer is the fragment index, the second integer is a total count of objects for the fragment, the third integer is a total size of the objects for the fragment. - */ - statsUpdate: number[]; - } - } - namespace NodeTracing { - interface TraceConfig { - /** - * Controls how the trace buffer stores data. - */ - recordMode?: string | undefined; - /** - * Included category filters. - */ - includedCategories: string[]; - } - interface StartParameterType { - traceConfig: TraceConfig; - } - interface GetCategoriesReturnType { - /** - * A list of supported tracing categories. - */ - categories: string[]; - } - interface DataCollectedEventDataType { - value: Array<{}>; - } - } - namespace NodeWorker { - type WorkerID = string; - /** - * Unique identifier of attached debugging session. - */ - type SessionID = string; - interface WorkerInfo { - workerId: WorkerID; - type: string; - title: string; - url: string; - } - interface SendMessageToWorkerParameterType { - message: string; - /** - * Identifier of the session. - */ - sessionId: SessionID; - } - interface EnableParameterType { - /** - * Whether to new workers should be paused until the frontend sends `Runtime.runIfWaitingForDebugger` - * message to run them. - */ - waitForDebuggerOnStart: boolean; - } - interface DetachParameterType { - sessionId: SessionID; - } - interface AttachedToWorkerEventDataType { - /** - * Identifier assigned to the session used to send/receive messages. - */ - sessionId: SessionID; - workerInfo: WorkerInfo; - waitingForDebugger: boolean; - } - interface DetachedFromWorkerEventDataType { - /** - * Detached session identifier. - */ - sessionId: SessionID; - } - interface ReceivedMessageFromWorkerEventDataType { - /** - * Identifier of a session which sends a message. - */ - sessionId: SessionID; - message: string; - } - } - namespace NodeRuntime { - interface NotifyWhenWaitingForDisconnectParameterType { - enabled: boolean; - } - } - /** - * The `inspector.Session` is used for dispatching messages to the V8 inspector - * back-end and receiving message responses and notifications. - */ - class Session extends EventEmitter { - /** - * Create a new instance of the inspector.Session class. - * The inspector session needs to be connected through session.connect() before the messages can be dispatched to the inspector backend. - */ - constructor(); - /** - * Connects a session to the inspector back-end. - * @since v8.0.0 - */ - connect(): void; - /** - * Immediately close the session. All pending message callbacks will be called - * with an error. `session.connect()` will need to be called to be able to send - * messages again. Reconnected session will lose all inspector state, such as - * enabled agents or configured breakpoints. - * @since v8.0.0 - */ - disconnect(): void; - /** - * Posts a message to the inspector back-end. `callback` will be notified when - * a response is received. `callback` is a function that accepts two optional - * arguments: error and message-specific result. - * - * ```js - * session.post('Runtime.evaluate', { expression: '2 + 2' }, - * (error, { result }) => console.log(result)); - * // Output: { type: 'number', value: 4, description: '4' } - * ``` - * - * The latest version of the V8 inspector protocol is published on the [Chrome DevTools Protocol Viewer](https://chromedevtools.github.io/devtools-protocol/v8/). - * - * Node.js inspector supports all the Chrome DevTools Protocol domains declared - * by V8\. Chrome DevTools Protocol domain provides an interface for interacting - * with one of the runtime agents used to inspect the application state and listen - * to the run-time events. - * - * ## Example usage - * - * Apart from the debugger, various V8 Profilers are available through the DevTools - * protocol. - * @since v8.0.0 - */ - post(method: string, params?: {}, callback?: (err: Error | null, params?: {}) => void): void; - post(method: string, callback?: (err: Error | null, params?: {}) => void): void; - /** - * Returns supported domains. - */ - post( - method: 'Schema.getDomains', - callback?: (err: Error | null, params: Schema.GetDomainsReturnType) => void, - ): void; - /** - * Evaluates expression on global object. - */ - post( - method: 'Runtime.evaluate', - params?: Runtime.EvaluateParameterType, - callback?: (err: Error | null, params: Runtime.EvaluateReturnType) => void, - ): void; - post(method: 'Runtime.evaluate', callback?: (err: Error | null, params: Runtime.EvaluateReturnType) => void): void; - /** - * Add handler to promise with given promise object id. - */ - post( - method: 'Runtime.awaitPromise', - params?: Runtime.AwaitPromiseParameterType, - callback?: (err: Error | null, params: Runtime.AwaitPromiseReturnType) => void, - ): void; - post( - method: 'Runtime.awaitPromise', - callback?: (err: Error | null, params: Runtime.AwaitPromiseReturnType) => void, - ): void; - /** - * Calls function with given declaration on the given object. Object group of the result is inherited from the target object. - */ - post( - method: 'Runtime.callFunctionOn', - params?: Runtime.CallFunctionOnParameterType, - callback?: (err: Error | null, params: Runtime.CallFunctionOnReturnType) => void, - ): void; - post( - method: 'Runtime.callFunctionOn', - callback?: (err: Error | null, params: Runtime.CallFunctionOnReturnType) => void, - ): void; - /** - * Returns properties of a given object. Object group of the result is inherited from the target object. - */ - post( - method: 'Runtime.getProperties', - params?: Runtime.GetPropertiesParameterType, - callback?: (err: Error | null, params: Runtime.GetPropertiesReturnType) => void, - ): void; - post( - method: 'Runtime.getProperties', - callback?: (err: Error | null, params: Runtime.GetPropertiesReturnType) => void, - ): void; - /** - * Releases remote object with given id. - */ - post( - method: 'Runtime.releaseObject', - params?: Runtime.ReleaseObjectParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Runtime.releaseObject', callback?: (err: Error | null) => void): void; - /** - * Releases all remote objects that belong to a given group. - */ - post( - method: 'Runtime.releaseObjectGroup', - params?: Runtime.ReleaseObjectGroupParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Runtime.releaseObjectGroup', callback?: (err: Error | null) => void): void; - /** - * Tells inspected instance to run if it was waiting for debugger to attach. - */ - post(method: 'Runtime.runIfWaitingForDebugger', callback?: (err: Error | null) => void): void; - /** - * Enables reporting of execution contexts creation by means of executionContextCreated event. When the reporting gets enabled the event will be sent immediately for each existing execution context. - */ - post(method: 'Runtime.enable', callback?: (err: Error | null) => void): void; - /** - * Disables reporting of execution contexts creation. - */ - post(method: 'Runtime.disable', callback?: (err: Error | null) => void): void; - /** - * Discards collected exceptions and console API calls. - */ - post(method: 'Runtime.discardConsoleEntries', callback?: (err: Error | null) => void): void; - /** - * @experimental - */ - post( - method: 'Runtime.setCustomObjectFormatterEnabled', - params?: Runtime.SetCustomObjectFormatterEnabledParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Runtime.setCustomObjectFormatterEnabled', callback?: (err: Error | null) => void): void; - /** - * Compiles expression. - */ - post( - method: 'Runtime.compileScript', - params?: Runtime.CompileScriptParameterType, - callback?: (err: Error | null, params: Runtime.CompileScriptReturnType) => void, - ): void; - post( - method: 'Runtime.compileScript', - callback?: (err: Error | null, params: Runtime.CompileScriptReturnType) => void, - ): void; - /** - * Runs script with given id in a given context. - */ - post( - method: 'Runtime.runScript', - params?: Runtime.RunScriptParameterType, - callback?: (err: Error | null, params: Runtime.RunScriptReturnType) => void, - ): void; - post( - method: 'Runtime.runScript', - callback?: (err: Error | null, params: Runtime.RunScriptReturnType) => void, - ): void; - post( - method: 'Runtime.queryObjects', - params?: Runtime.QueryObjectsParameterType, - callback?: (err: Error | null, params: Runtime.QueryObjectsReturnType) => void, - ): void; - post( - method: 'Runtime.queryObjects', - callback?: (err: Error | null, params: Runtime.QueryObjectsReturnType) => void, - ): void; - /** - * Returns all let, const and class variables from global scope. - */ - post( - method: 'Runtime.globalLexicalScopeNames', - params?: Runtime.GlobalLexicalScopeNamesParameterType, - callback?: (err: Error | null, params: Runtime.GlobalLexicalScopeNamesReturnType) => void, - ): void; - post( - method: 'Runtime.globalLexicalScopeNames', - callback?: (err: Error | null, params: Runtime.GlobalLexicalScopeNamesReturnType) => void, - ): void; - /** - * Enables debugger for the given page. Clients should not assume that the debugging has been enabled until the result for this command is received. - */ - post(method: 'Debugger.enable', callback?: (err: Error | null, params: Debugger.EnableReturnType) => void): void; - /** - * Disables debugger for given page. - */ - post(method: 'Debugger.disable', callback?: (err: Error | null) => void): void; - /** - * Activates / deactivates all breakpoints on the page. - */ - post( - method: 'Debugger.setBreakpointsActive', - params?: Debugger.SetBreakpointsActiveParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Debugger.setBreakpointsActive', callback?: (err: Error | null) => void): void; - /** - * Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc). - */ - post( - method: 'Debugger.setSkipAllPauses', - params?: Debugger.SetSkipAllPausesParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Debugger.setSkipAllPauses', callback?: (err: Error | null) => void): void; - /** - * Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this command is issued, all existing parsed scripts will have breakpoints resolved and returned in locations property. Further matching script parsing will result in subsequent breakpointResolved events issued. This logical breakpoint will survive page reloads. - */ - post( - method: 'Debugger.setBreakpointByUrl', - params?: Debugger.SetBreakpointByUrlParameterType, - callback?: (err: Error | null, params: Debugger.SetBreakpointByUrlReturnType) => void, - ): void; - post( - method: 'Debugger.setBreakpointByUrl', - callback?: (err: Error | null, params: Debugger.SetBreakpointByUrlReturnType) => void, - ): void; - /** - * Sets JavaScript breakpoint at a given location. - */ - post( - method: 'Debugger.setBreakpoint', - params?: Debugger.SetBreakpointParameterType, - callback?: (err: Error | null, params: Debugger.SetBreakpointReturnType) => void, - ): void; - post( - method: 'Debugger.setBreakpoint', - callback?: (err: Error | null, params: Debugger.SetBreakpointReturnType) => void, - ): void; - /** - * Removes JavaScript breakpoint. - */ - post( - method: 'Debugger.removeBreakpoint', - params?: Debugger.RemoveBreakpointParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Debugger.removeBreakpoint', callback?: (err: Error | null) => void): void; - /** - * Returns possible locations for breakpoint. scriptId in start and end range locations should be the same. - */ - post( - method: 'Debugger.getPossibleBreakpoints', - params?: Debugger.GetPossibleBreakpointsParameterType, - callback?: (err: Error | null, params: Debugger.GetPossibleBreakpointsReturnType) => void, - ): void; - post( - method: 'Debugger.getPossibleBreakpoints', - callback?: (err: Error | null, params: Debugger.GetPossibleBreakpointsReturnType) => void, - ): void; - /** - * Continues execution until specific location is reached. - */ - post( - method: 'Debugger.continueToLocation', - params?: Debugger.ContinueToLocationParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Debugger.continueToLocation', callback?: (err: Error | null) => void): void; - /** - * @experimental - */ - post( - method: 'Debugger.pauseOnAsyncCall', - params?: Debugger.PauseOnAsyncCallParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Debugger.pauseOnAsyncCall', callback?: (err: Error | null) => void): void; - /** - * Steps over the statement. - */ - post(method: 'Debugger.stepOver', callback?: (err: Error | null) => void): void; - /** - * Steps into the function call. - */ - post( - method: 'Debugger.stepInto', - params?: Debugger.StepIntoParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Debugger.stepInto', callback?: (err: Error | null) => void): void; - /** - * Steps out of the function call. - */ - post(method: 'Debugger.stepOut', callback?: (err: Error | null) => void): void; - /** - * Stops on the next JavaScript statement. - */ - post(method: 'Debugger.pause', callback?: (err: Error | null) => void): void; - /** - * This method is deprecated - use Debugger.stepInto with breakOnAsyncCall and Debugger.pauseOnAsyncTask instead. Steps into next scheduled async task if any is scheduled before next pause. Returns success when async task is actually scheduled, returns error if no task were scheduled or another scheduleStepIntoAsync was called. - * @experimental - */ - post(method: 'Debugger.scheduleStepIntoAsync', callback?: (err: Error | null) => void): void; - /** - * Resumes JavaScript execution. - */ - post(method: 'Debugger.resume', callback?: (err: Error | null) => void): void; - /** - * Returns stack trace with given stackTraceId. - * @experimental - */ - post( - method: 'Debugger.getStackTrace', - params?: Debugger.GetStackTraceParameterType, - callback?: (err: Error | null, params: Debugger.GetStackTraceReturnType) => void, - ): void; - post( - method: 'Debugger.getStackTrace', - callback?: (err: Error | null, params: Debugger.GetStackTraceReturnType) => void, - ): void; - /** - * Searches for given string in script content. - */ - post( - method: 'Debugger.searchInContent', - params?: Debugger.SearchInContentParameterType, - callback?: (err: Error | null, params: Debugger.SearchInContentReturnType) => void, - ): void; - post( - method: 'Debugger.searchInContent', - callback?: (err: Error | null, params: Debugger.SearchInContentReturnType) => void, - ): void; - /** - * Edits JavaScript source live. - */ - post( - method: 'Debugger.setScriptSource', - params?: Debugger.SetScriptSourceParameterType, - callback?: (err: Error | null, params: Debugger.SetScriptSourceReturnType) => void, - ): void; - post( - method: 'Debugger.setScriptSource', - callback?: (err: Error | null, params: Debugger.SetScriptSourceReturnType) => void, - ): void; - /** - * Restarts particular call frame from the beginning. - */ - post( - method: 'Debugger.restartFrame', - params?: Debugger.RestartFrameParameterType, - callback?: (err: Error | null, params: Debugger.RestartFrameReturnType) => void, - ): void; - post( - method: 'Debugger.restartFrame', - callback?: (err: Error | null, params: Debugger.RestartFrameReturnType) => void, - ): void; - /** - * Returns source for the script with given id. - */ - post( - method: 'Debugger.getScriptSource', - params?: Debugger.GetScriptSourceParameterType, - callback?: (err: Error | null, params: Debugger.GetScriptSourceReturnType) => void, - ): void; - post( - method: 'Debugger.getScriptSource', - callback?: (err: Error | null, params: Debugger.GetScriptSourceReturnType) => void, - ): void; - /** - * Defines pause on exceptions state. Can be set to stop on all exceptions, uncaught exceptions or no exceptions. Initial pause on exceptions state is none. - */ - post( - method: 'Debugger.setPauseOnExceptions', - params?: Debugger.SetPauseOnExceptionsParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Debugger.setPauseOnExceptions', callback?: (err: Error | null) => void): void; - /** - * Evaluates expression on a given call frame. - */ - post( - method: 'Debugger.evaluateOnCallFrame', - params?: Debugger.EvaluateOnCallFrameParameterType, - callback?: (err: Error | null, params: Debugger.EvaluateOnCallFrameReturnType) => void, - ): void; - post( - method: 'Debugger.evaluateOnCallFrame', - callback?: (err: Error | null, params: Debugger.EvaluateOnCallFrameReturnType) => void, - ): void; - /** - * Changes value of variable in a callframe. Object-based scopes are not supported and must be mutated manually. - */ - post( - method: 'Debugger.setVariableValue', - params?: Debugger.SetVariableValueParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Debugger.setVariableValue', callback?: (err: Error | null) => void): void; - /** - * Changes return value in top frame. Available only at return break position. - * @experimental - */ - post( - method: 'Debugger.setReturnValue', - params?: Debugger.SetReturnValueParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Debugger.setReturnValue', callback?: (err: Error | null) => void): void; - /** - * Enables or disables async call stacks tracking. - */ - post( - method: 'Debugger.setAsyncCallStackDepth', - params?: Debugger.SetAsyncCallStackDepthParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Debugger.setAsyncCallStackDepth', callback?: (err: Error | null) => void): void; - /** - * Replace previous blackbox patterns with passed ones. Forces backend to skip stepping/pausing in scripts with url matching one of the patterns. VM will try to leave blackboxed script by performing 'step in' several times, finally resorting to 'step out' if unsuccessful. - * @experimental - */ - post( - method: 'Debugger.setBlackboxPatterns', - params?: Debugger.SetBlackboxPatternsParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Debugger.setBlackboxPatterns', callback?: (err: Error | null) => void): void; - /** - * Makes backend skip steps in the script in blackboxed ranges. VM will try leave blacklisted scripts by performing 'step in' several times, finally resorting to 'step out' if unsuccessful. Positions array contains positions where blackbox state is changed. First interval isn't blackboxed. Array should be sorted. - * @experimental - */ - post( - method: 'Debugger.setBlackboxedRanges', - params?: Debugger.SetBlackboxedRangesParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Debugger.setBlackboxedRanges', callback?: (err: Error | null) => void): void; - /** - * Enables console domain, sends the messages collected so far to the client by means of the messageAdded notification. - */ - post(method: 'Console.enable', callback?: (err: Error | null) => void): void; - /** - * Disables console domain, prevents further console messages from being reported to the client. - */ - post(method: 'Console.disable', callback?: (err: Error | null) => void): void; - /** - * Does nothing. - */ - post(method: 'Console.clearMessages', callback?: (err: Error | null) => void): void; - post(method: 'Profiler.enable', callback?: (err: Error | null) => void): void; - post(method: 'Profiler.disable', callback?: (err: Error | null) => void): void; - /** - * Changes CPU profiler sampling interval. Must be called before CPU profiles recording started. - */ - post( - method: 'Profiler.setSamplingInterval', - params?: Profiler.SetSamplingIntervalParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Profiler.setSamplingInterval', callback?: (err: Error | null) => void): void; - post(method: 'Profiler.start', callback?: (err: Error | null) => void): void; - post(method: 'Profiler.stop', callback?: (err: Error | null, params: Profiler.StopReturnType) => void): void; - /** - * Enable precise code coverage. Coverage data for JavaScript executed before enabling precise code coverage may be incomplete. Enabling prevents running optimized code and resets execution counters. - */ - post( - method: 'Profiler.startPreciseCoverage', - params?: Profiler.StartPreciseCoverageParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'Profiler.startPreciseCoverage', callback?: (err: Error | null) => void): void; - /** - * Disable precise code coverage. Disabling releases unnecessary execution count records and allows executing optimized code. - */ - post(method: 'Profiler.stopPreciseCoverage', callback?: (err: Error | null) => void): void; - /** - * Collect coverage data for the current isolate, and resets execution counters. Precise code coverage needs to have started. - */ - post( - method: 'Profiler.takePreciseCoverage', - callback?: (err: Error | null, params: Profiler.TakePreciseCoverageReturnType) => void, - ): void; - /** - * Collect coverage data for the current isolate. The coverage data may be incomplete due to garbage collection. - */ - post( - method: 'Profiler.getBestEffortCoverage', - callback?: (err: Error | null, params: Profiler.GetBestEffortCoverageReturnType) => void, - ): void; - /** - * Enable type profile. - * @experimental - */ - post(method: 'Profiler.startTypeProfile', callback?: (err: Error | null) => void): void; - /** - * Disable type profile. Disabling releases type profile data collected so far. - * @experimental - */ - post(method: 'Profiler.stopTypeProfile', callback?: (err: Error | null) => void): void; - /** - * Collect type profile. - * @experimental - */ - post( - method: 'Profiler.takeTypeProfile', - callback?: (err: Error | null, params: Profiler.TakeTypeProfileReturnType) => void, - ): void; - post(method: 'HeapProfiler.enable', callback?: (err: Error | null) => void): void; - post(method: 'HeapProfiler.disable', callback?: (err: Error | null) => void): void; - post( - method: 'HeapProfiler.startTrackingHeapObjects', - params?: HeapProfiler.StartTrackingHeapObjectsParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'HeapProfiler.startTrackingHeapObjects', callback?: (err: Error | null) => void): void; - post( - method: 'HeapProfiler.stopTrackingHeapObjects', - params?: HeapProfiler.StopTrackingHeapObjectsParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'HeapProfiler.stopTrackingHeapObjects', callback?: (err: Error | null) => void): void; - post( - method: 'HeapProfiler.takeHeapSnapshot', - params?: HeapProfiler.TakeHeapSnapshotParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'HeapProfiler.takeHeapSnapshot', callback?: (err: Error | null) => void): void; - post(method: 'HeapProfiler.collectGarbage', callback?: (err: Error | null) => void): void; - post( - method: 'HeapProfiler.getObjectByHeapObjectId', - params?: HeapProfiler.GetObjectByHeapObjectIdParameterType, - callback?: (err: Error | null, params: HeapProfiler.GetObjectByHeapObjectIdReturnType) => void, - ): void; - post( - method: 'HeapProfiler.getObjectByHeapObjectId', - callback?: (err: Error | null, params: HeapProfiler.GetObjectByHeapObjectIdReturnType) => void, - ): void; - /** - * Enables console to refer to the node with given id via $x (see Command Line API for more details $x functions). - */ - post( - method: 'HeapProfiler.addInspectedHeapObject', - params?: HeapProfiler.AddInspectedHeapObjectParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'HeapProfiler.addInspectedHeapObject', callback?: (err: Error | null) => void): void; - post( - method: 'HeapProfiler.getHeapObjectId', - params?: HeapProfiler.GetHeapObjectIdParameterType, - callback?: (err: Error | null, params: HeapProfiler.GetHeapObjectIdReturnType) => void, - ): void; - post( - method: 'HeapProfiler.getHeapObjectId', - callback?: (err: Error | null, params: HeapProfiler.GetHeapObjectIdReturnType) => void, - ): void; - post( - method: 'HeapProfiler.startSampling', - params?: HeapProfiler.StartSamplingParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'HeapProfiler.startSampling', callback?: (err: Error | null) => void): void; - post( - method: 'HeapProfiler.stopSampling', - callback?: (err: Error | null, params: HeapProfiler.StopSamplingReturnType) => void, - ): void; - post( - method: 'HeapProfiler.getSamplingProfile', - callback?: (err: Error | null, params: HeapProfiler.GetSamplingProfileReturnType) => void, - ): void; - /** - * Gets supported tracing categories. - */ - post( - method: 'NodeTracing.getCategories', - callback?: (err: Error | null, params: NodeTracing.GetCategoriesReturnType) => void, - ): void; - /** - * Start trace events collection. - */ - post( - method: 'NodeTracing.start', - params?: NodeTracing.StartParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'NodeTracing.start', callback?: (err: Error | null) => void): void; - /** - * Stop trace events collection. Remaining collected events will be sent as a sequence of - * dataCollected events followed by tracingComplete event. - */ - post(method: 'NodeTracing.stop', callback?: (err: Error | null) => void): void; - /** - * Sends protocol message over session with given id. - */ - post( - method: 'NodeWorker.sendMessageToWorker', - params?: NodeWorker.SendMessageToWorkerParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'NodeWorker.sendMessageToWorker', callback?: (err: Error | null) => void): void; - /** - * Instructs the inspector to attach to running workers. Will also attach to new workers - * as they start - */ - post( - method: 'NodeWorker.enable', - params?: NodeWorker.EnableParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'NodeWorker.enable', callback?: (err: Error | null) => void): void; - /** - * Detaches from all running workers and disables attaching to new workers as they are started. - */ - post(method: 'NodeWorker.disable', callback?: (err: Error | null) => void): void; - /** - * Detached from the worker with given sessionId. - */ - post( - method: 'NodeWorker.detach', - params?: NodeWorker.DetachParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'NodeWorker.detach', callback?: (err: Error | null) => void): void; - /** - * Enable the `NodeRuntime.waitingForDisconnect`. - */ - post( - method: 'NodeRuntime.notifyWhenWaitingForDisconnect', - params?: NodeRuntime.NotifyWhenWaitingForDisconnectParameterType, - callback?: (err: Error | null) => void, - ): void; - post(method: 'NodeRuntime.notifyWhenWaitingForDisconnect', callback?: (err: Error | null) => void): void; - // Events - addListener(event: string, listener: (...args: any[]) => void): this; - /** - * Emitted when any notification from the V8 Inspector is received. - */ - addListener(event: 'inspectorNotification', listener: (message: InspectorNotification<{}>) => void): this; - /** - * Issued when new execution context is created. - */ - addListener( - event: 'Runtime.executionContextCreated', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when execution context is destroyed. - */ - addListener( - event: 'Runtime.executionContextDestroyed', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when all executionContexts were cleared in browser - */ - addListener(event: 'Runtime.executionContextsCleared', listener: () => void): this; - /** - * Issued when exception was thrown and unhandled. - */ - addListener( - event: 'Runtime.exceptionThrown', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when unhandled exception was revoked. - */ - addListener( - event: 'Runtime.exceptionRevoked', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when console API was called. - */ - addListener( - event: 'Runtime.consoleAPICalled', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when object should be inspected (for example, as a result of inspect() command line API call). - */ - addListener( - event: 'Runtime.inspectRequested', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. - */ - addListener( - event: 'Debugger.scriptParsed', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when virtual machine fails to parse the script. - */ - addListener( - event: 'Debugger.scriptFailedToParse', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when breakpoint is resolved to an actual script and location. - */ - addListener( - event: 'Debugger.breakpointResolved', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. - */ - addListener( - event: 'Debugger.paused', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when the virtual machine resumed execution. - */ - addListener(event: 'Debugger.resumed', listener: () => void): this; - /** - * Issued when new console message is added. - */ - addListener( - event: 'Console.messageAdded', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Sent when new profile recording is started using console.profile() call. - */ - addListener( - event: 'Profiler.consoleProfileStarted', - listener: (message: InspectorNotification) => void, - ): this; - addListener( - event: 'Profiler.consoleProfileFinished', - listener: (message: InspectorNotification) => void, - ): this; - addListener( - event: 'HeapProfiler.addHeapSnapshotChunk', - listener: (message: InspectorNotification) => void, - ): this; - addListener(event: 'HeapProfiler.resetProfiles', listener: () => void): this; - addListener( - event: 'HeapProfiler.reportHeapSnapshotProgress', - listener: (message: InspectorNotification) => void, - ): this; - /** - * If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. - */ - addListener( - event: 'HeapProfiler.lastSeenObjectId', - listener: (message: InspectorNotification) => void, - ): this; - /** - * If heap objects tracking has been started then backend may send update for one or more fragments - */ - addListener( - event: 'HeapProfiler.heapStatsUpdate', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Contains an bucket of collected trace events. - */ - addListener( - event: 'NodeTracing.dataCollected', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Signals that tracing is stopped and there is no trace buffers pending flush, all data were - * delivered via dataCollected events. - */ - addListener(event: 'NodeTracing.tracingComplete', listener: () => void): this; - /** - * Issued when attached to a worker. - */ - addListener( - event: 'NodeWorker.attachedToWorker', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when detached from the worker. - */ - addListener( - event: 'NodeWorker.detachedFromWorker', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Notifies about a new protocol message received from the session - * (session ID is provided in attachedToWorker notification). - */ - addListener( - event: 'NodeWorker.receivedMessageFromWorker', - listener: (message: InspectorNotification) => void, - ): this; - /** - * This event is fired instead of `Runtime.executionContextDestroyed` when - * enabled. - * It is fired when the Node process finished all code execution and is - * waiting for all frontends to disconnect. - */ - addListener(event: 'NodeRuntime.waitingForDisconnect', listener: () => void): this; - emit(event: string | symbol, ...args: any[]): boolean; - emit(event: 'inspectorNotification', message: InspectorNotification<{}>): boolean; - emit( - event: 'Runtime.executionContextCreated', - message: InspectorNotification, - ): boolean; - emit( - event: 'Runtime.executionContextDestroyed', - message: InspectorNotification, - ): boolean; - emit(event: 'Runtime.executionContextsCleared'): boolean; - emit( - event: 'Runtime.exceptionThrown', - message: InspectorNotification, - ): boolean; - emit( - event: 'Runtime.exceptionRevoked', - message: InspectorNotification, - ): boolean; - emit( - event: 'Runtime.consoleAPICalled', - message: InspectorNotification, - ): boolean; - emit( - event: 'Runtime.inspectRequested', - message: InspectorNotification, - ): boolean; - emit(event: 'Debugger.scriptParsed', message: InspectorNotification): boolean; - emit( - event: 'Debugger.scriptFailedToParse', - message: InspectorNotification, - ): boolean; - emit( - event: 'Debugger.breakpointResolved', - message: InspectorNotification, - ): boolean; - emit(event: 'Debugger.paused', message: InspectorNotification): boolean; - emit(event: 'Debugger.resumed'): boolean; - emit(event: 'Console.messageAdded', message: InspectorNotification): boolean; - emit( - event: 'Profiler.consoleProfileStarted', - message: InspectorNotification, - ): boolean; - emit( - event: 'Profiler.consoleProfileFinished', - message: InspectorNotification, - ): boolean; - emit( - event: 'HeapProfiler.addHeapSnapshotChunk', - message: InspectorNotification, - ): boolean; - emit(event: 'HeapProfiler.resetProfiles'): boolean; - emit( - event: 'HeapProfiler.reportHeapSnapshotProgress', - message: InspectorNotification, - ): boolean; - emit( - event: 'HeapProfiler.lastSeenObjectId', - message: InspectorNotification, - ): boolean; - emit( - event: 'HeapProfiler.heapStatsUpdate', - message: InspectorNotification, - ): boolean; - emit( - event: 'NodeTracing.dataCollected', - message: InspectorNotification, - ): boolean; - emit(event: 'NodeTracing.tracingComplete'): boolean; - emit( - event: 'NodeWorker.attachedToWorker', - message: InspectorNotification, - ): boolean; - emit( - event: 'NodeWorker.detachedFromWorker', - message: InspectorNotification, - ): boolean; - emit( - event: 'NodeWorker.receivedMessageFromWorker', - message: InspectorNotification, - ): boolean; - emit(event: 'NodeRuntime.waitingForDisconnect'): boolean; - on(event: string, listener: (...args: any[]) => void): this; - /** - * Emitted when any notification from the V8 Inspector is received. - */ - on(event: 'inspectorNotification', listener: (message: InspectorNotification<{}>) => void): this; - /** - * Issued when new execution context is created. - */ - on( - event: 'Runtime.executionContextCreated', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when execution context is destroyed. - */ - on( - event: 'Runtime.executionContextDestroyed', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when all executionContexts were cleared in browser - */ - on(event: 'Runtime.executionContextsCleared', listener: () => void): this; - /** - * Issued when exception was thrown and unhandled. - */ - on( - event: 'Runtime.exceptionThrown', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when unhandled exception was revoked. - */ - on( - event: 'Runtime.exceptionRevoked', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when console API was called. - */ - on( - event: 'Runtime.consoleAPICalled', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when object should be inspected (for example, as a result of inspect() command line API call). - */ - on( - event: 'Runtime.inspectRequested', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. - */ - on( - event: 'Debugger.scriptParsed', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when virtual machine fails to parse the script. - */ - on( - event: 'Debugger.scriptFailedToParse', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when breakpoint is resolved to an actual script and location. - */ - on( - event: 'Debugger.breakpointResolved', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. - */ - on( - event: 'Debugger.paused', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when the virtual machine resumed execution. - */ - on(event: 'Debugger.resumed', listener: () => void): this; - /** - * Issued when new console message is added. - */ - on( - event: 'Console.messageAdded', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Sent when new profile recording is started using console.profile() call. - */ - on( - event: 'Profiler.consoleProfileStarted', - listener: (message: InspectorNotification) => void, - ): this; - on( - event: 'Profiler.consoleProfileFinished', - listener: (message: InspectorNotification) => void, - ): this; - on( - event: 'HeapProfiler.addHeapSnapshotChunk', - listener: (message: InspectorNotification) => void, - ): this; - on(event: 'HeapProfiler.resetProfiles', listener: () => void): this; - on( - event: 'HeapProfiler.reportHeapSnapshotProgress', - listener: (message: InspectorNotification) => void, - ): this; - /** - * If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. - */ - on( - event: 'HeapProfiler.lastSeenObjectId', - listener: (message: InspectorNotification) => void, - ): this; - /** - * If heap objects tracking has been started then backend may send update for one or more fragments - */ - on( - event: 'HeapProfiler.heapStatsUpdate', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Contains an bucket of collected trace events. - */ - on( - event: 'NodeTracing.dataCollected', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Signals that tracing is stopped and there is no trace buffers pending flush, all data were - * delivered via dataCollected events. - */ - on(event: 'NodeTracing.tracingComplete', listener: () => void): this; - /** - * Issued when attached to a worker. - */ - on( - event: 'NodeWorker.attachedToWorker', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when detached from the worker. - */ - on( - event: 'NodeWorker.detachedFromWorker', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Notifies about a new protocol message received from the session - * (session ID is provided in attachedToWorker notification). - */ - on( - event: 'NodeWorker.receivedMessageFromWorker', - listener: (message: InspectorNotification) => void, - ): this; - /** - * This event is fired instead of `Runtime.executionContextDestroyed` when - * enabled. - * It is fired when the Node process finished all code execution and is - * waiting for all frontends to disconnect. - */ - on(event: 'NodeRuntime.waitingForDisconnect', listener: () => void): this; - once(event: string, listener: (...args: any[]) => void): this; - /** - * Emitted when any notification from the V8 Inspector is received. - */ - once(event: 'inspectorNotification', listener: (message: InspectorNotification<{}>) => void): this; - /** - * Issued when new execution context is created. - */ - once( - event: 'Runtime.executionContextCreated', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when execution context is destroyed. - */ - once( - event: 'Runtime.executionContextDestroyed', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when all executionContexts were cleared in browser - */ - once(event: 'Runtime.executionContextsCleared', listener: () => void): this; - /** - * Issued when exception was thrown and unhandled. - */ - once( - event: 'Runtime.exceptionThrown', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when unhandled exception was revoked. - */ - once( - event: 'Runtime.exceptionRevoked', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when console API was called. - */ - once( - event: 'Runtime.consoleAPICalled', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when object should be inspected (for example, as a result of inspect() command line API call). - */ - once( - event: 'Runtime.inspectRequested', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. - */ - once( - event: 'Debugger.scriptParsed', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when virtual machine fails to parse the script. - */ - once( - event: 'Debugger.scriptFailedToParse', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when breakpoint is resolved to an actual script and location. - */ - once( - event: 'Debugger.breakpointResolved', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. - */ - once( - event: 'Debugger.paused', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when the virtual machine resumed execution. - */ - once(event: 'Debugger.resumed', listener: () => void): this; - /** - * Issued when new console message is added. - */ - once( - event: 'Console.messageAdded', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Sent when new profile recording is started using console.profile() call. - */ - once( - event: 'Profiler.consoleProfileStarted', - listener: (message: InspectorNotification) => void, - ): this; - once( - event: 'Profiler.consoleProfileFinished', - listener: (message: InspectorNotification) => void, - ): this; - once( - event: 'HeapProfiler.addHeapSnapshotChunk', - listener: (message: InspectorNotification) => void, - ): this; - once(event: 'HeapProfiler.resetProfiles', listener: () => void): this; - once( - event: 'HeapProfiler.reportHeapSnapshotProgress', - listener: (message: InspectorNotification) => void, - ): this; - /** - * If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. - */ - once( - event: 'HeapProfiler.lastSeenObjectId', - listener: (message: InspectorNotification) => void, - ): this; - /** - * If heap objects tracking has been started then backend may send update for one or more fragments - */ - once( - event: 'HeapProfiler.heapStatsUpdate', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Contains an bucket of collected trace events. - */ - once( - event: 'NodeTracing.dataCollected', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Signals that tracing is stopped and there is no trace buffers pending flush, all data were - * delivered via dataCollected events. - */ - once(event: 'NodeTracing.tracingComplete', listener: () => void): this; - /** - * Issued when attached to a worker. - */ - once( - event: 'NodeWorker.attachedToWorker', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when detached from the worker. - */ - once( - event: 'NodeWorker.detachedFromWorker', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Notifies about a new protocol message received from the session - * (session ID is provided in attachedToWorker notification). - */ - once( - event: 'NodeWorker.receivedMessageFromWorker', - listener: (message: InspectorNotification) => void, - ): this; - /** - * This event is fired instead of `Runtime.executionContextDestroyed` when - * enabled. - * It is fired when the Node process finished all code execution and is - * waiting for all frontends to disconnect. - */ - once(event: 'NodeRuntime.waitingForDisconnect', listener: () => void): this; - prependListener(event: string, listener: (...args: any[]) => void): this; - /** - * Emitted when any notification from the V8 Inspector is received. - */ - prependListener(event: 'inspectorNotification', listener: (message: InspectorNotification<{}>) => void): this; - /** - * Issued when new execution context is created. - */ - prependListener( - event: 'Runtime.executionContextCreated', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when execution context is destroyed. - */ - prependListener( - event: 'Runtime.executionContextDestroyed', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when all executionContexts were cleared in browser - */ - prependListener(event: 'Runtime.executionContextsCleared', listener: () => void): this; - /** - * Issued when exception was thrown and unhandled. - */ - prependListener( - event: 'Runtime.exceptionThrown', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when unhandled exception was revoked. - */ - prependListener( - event: 'Runtime.exceptionRevoked', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when console API was called. - */ - prependListener( - event: 'Runtime.consoleAPICalled', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when object should be inspected (for example, as a result of inspect() command line API call). - */ - prependListener( - event: 'Runtime.inspectRequested', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. - */ - prependListener( - event: 'Debugger.scriptParsed', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when virtual machine fails to parse the script. - */ - prependListener( - event: 'Debugger.scriptFailedToParse', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when breakpoint is resolved to an actual script and location. - */ - prependListener( - event: 'Debugger.breakpointResolved', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. - */ - prependListener( - event: 'Debugger.paused', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when the virtual machine resumed execution. - */ - prependListener(event: 'Debugger.resumed', listener: () => void): this; - /** - * Issued when new console message is added. - */ - prependListener( - event: 'Console.messageAdded', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Sent when new profile recording is started using console.profile() call. - */ - prependListener( - event: 'Profiler.consoleProfileStarted', - listener: (message: InspectorNotification) => void, - ): this; - prependListener( - event: 'Profiler.consoleProfileFinished', - listener: (message: InspectorNotification) => void, - ): this; - prependListener( - event: 'HeapProfiler.addHeapSnapshotChunk', - listener: (message: InspectorNotification) => void, - ): this; - prependListener(event: 'HeapProfiler.resetProfiles', listener: () => void): this; - prependListener( - event: 'HeapProfiler.reportHeapSnapshotProgress', - listener: (message: InspectorNotification) => void, - ): this; - /** - * If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. - */ - prependListener( - event: 'HeapProfiler.lastSeenObjectId', - listener: (message: InspectorNotification) => void, - ): this; - /** - * If heap objects tracking has been started then backend may send update for one or more fragments - */ - prependListener( - event: 'HeapProfiler.heapStatsUpdate', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Contains an bucket of collected trace events. - */ - prependListener( - event: 'NodeTracing.dataCollected', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Signals that tracing is stopped and there is no trace buffers pending flush, all data were - * delivered via dataCollected events. - */ - prependListener(event: 'NodeTracing.tracingComplete', listener: () => void): this; - /** - * Issued when attached to a worker. - */ - prependListener( - event: 'NodeWorker.attachedToWorker', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when detached from the worker. - */ - prependListener( - event: 'NodeWorker.detachedFromWorker', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Notifies about a new protocol message received from the session - * (session ID is provided in attachedToWorker notification). - */ - prependListener( - event: 'NodeWorker.receivedMessageFromWorker', - listener: (message: InspectorNotification) => void, - ): this; - /** - * This event is fired instead of `Runtime.executionContextDestroyed` when - * enabled. - * It is fired when the Node process finished all code execution and is - * waiting for all frontends to disconnect. - */ - prependListener(event: 'NodeRuntime.waitingForDisconnect', listener: () => void): this; - prependOnceListener(event: string, listener: (...args: any[]) => void): this; - /** - * Emitted when any notification from the V8 Inspector is received. - */ - prependOnceListener(event: 'inspectorNotification', listener: (message: InspectorNotification<{}>) => void): this; - /** - * Issued when new execution context is created. - */ - prependOnceListener( - event: 'Runtime.executionContextCreated', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when execution context is destroyed. - */ - prependOnceListener( - event: 'Runtime.executionContextDestroyed', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when all executionContexts were cleared in browser - */ - prependOnceListener(event: 'Runtime.executionContextsCleared', listener: () => void): this; - /** - * Issued when exception was thrown and unhandled. - */ - prependOnceListener( - event: 'Runtime.exceptionThrown', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when unhandled exception was revoked. - */ - prependOnceListener( - event: 'Runtime.exceptionRevoked', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when console API was called. - */ - prependOnceListener( - event: 'Runtime.consoleAPICalled', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when object should be inspected (for example, as a result of inspect() command line API call). - */ - prependOnceListener( - event: 'Runtime.inspectRequested', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger. - */ - prependOnceListener( - event: 'Debugger.scriptParsed', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when virtual machine fails to parse the script. - */ - prependOnceListener( - event: 'Debugger.scriptFailedToParse', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when breakpoint is resolved to an actual script and location. - */ - prependOnceListener( - event: 'Debugger.breakpointResolved', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria. - */ - prependOnceListener( - event: 'Debugger.paused', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Fired when the virtual machine resumed execution. - */ - prependOnceListener(event: 'Debugger.resumed', listener: () => void): this; - /** - * Issued when new console message is added. - */ - prependOnceListener( - event: 'Console.messageAdded', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Sent when new profile recording is started using console.profile() call. - */ - prependOnceListener( - event: 'Profiler.consoleProfileStarted', - listener: (message: InspectorNotification) => void, - ): this; - prependOnceListener( - event: 'Profiler.consoleProfileFinished', - listener: (message: InspectorNotification) => void, - ): this; - prependOnceListener( - event: 'HeapProfiler.addHeapSnapshotChunk', - listener: (message: InspectorNotification) => void, - ): this; - prependOnceListener(event: 'HeapProfiler.resetProfiles', listener: () => void): this; - prependOnceListener( - event: 'HeapProfiler.reportHeapSnapshotProgress', - listener: (message: InspectorNotification) => void, - ): this; - /** - * If heap objects tracking has been started then backend regularly sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event. - */ - prependOnceListener( - event: 'HeapProfiler.lastSeenObjectId', - listener: (message: InspectorNotification) => void, - ): this; - /** - * If heap objects tracking has been started then backend may send update for one or more fragments - */ - prependOnceListener( - event: 'HeapProfiler.heapStatsUpdate', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Contains an bucket of collected trace events. - */ - prependOnceListener( - event: 'NodeTracing.dataCollected', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Signals that tracing is stopped and there is no trace buffers pending flush, all data were - * delivered via dataCollected events. - */ - prependOnceListener(event: 'NodeTracing.tracingComplete', listener: () => void): this; - /** - * Issued when attached to a worker. - */ - prependOnceListener( - event: 'NodeWorker.attachedToWorker', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Issued when detached from the worker. - */ - prependOnceListener( - event: 'NodeWorker.detachedFromWorker', - listener: (message: InspectorNotification) => void, - ): this; - /** - * Notifies about a new protocol message received from the session - * (session ID is provided in attachedToWorker notification). - */ - prependOnceListener( - event: 'NodeWorker.receivedMessageFromWorker', - listener: (message: InspectorNotification) => void, - ): this; - /** - * This event is fired instead of `Runtime.executionContextDestroyed` when - * enabled. - * It is fired when the Node process finished all code execution and is - * waiting for all frontends to disconnect. - */ - prependOnceListener(event: 'NodeRuntime.waitingForDisconnect', listener: () => void): this; - } - /** - * Activate inspector on host and port. Equivalent to`node --inspect=[[host:]port]`, but can be done programmatically after node has - * started. - * - * If wait is `true`, will block until a client has connected to the inspect port - * and flow control has been passed to the debugger client. - * - * See the `security warning` regarding the `host`parameter usage. - * @param [port='what was specified on the CLI'] Port to listen on for inspector connections. Optional. - * @param [host='what was specified on the CLI'] Host to listen on for inspector connections. Optional. - * @param [wait=false] Block until a client has connected. Optional. - */ - function open(port?: number, host?: string, wait?: boolean): void; - /** - * Deactivate the inspector. Blocks until there are no active connections. - */ - function close(): void; - /** - * Return the URL of the active inspector, or `undefined` if there is none. - * - * ```console - * $ node --inspect -p 'inspector.url()' - * Debugger listening on ws://127.0.0.1:9229/166e272e-7a30-4d09-97ce-f1c012b43c34 - * For help, see: https://nodejs.org/en/docs/inspector - * ws://127.0.0.1:9229/166e272e-7a30-4d09-97ce-f1c012b43c34 - * - * $ node --inspect=localhost:3000 -p 'inspector.url()' - * Debugger listening on ws://localhost:3000/51cf8d0e-3c36-4c59-8efd-54519839e56a - * For help, see: https://nodejs.org/en/docs/inspector - * ws://localhost:3000/51cf8d0e-3c36-4c59-8efd-54519839e56a - * - * $ node -p 'inspector.url()' - * undefined - * ``` - */ - function url(): string | undefined; - /** - * Blocks until a client (existing or connected later) has sent`Runtime.runIfWaitingForDebugger` command. - * - * An exception will be thrown if there is no active inspector. - * @since v12.7.0 - */ - function waitForDebugger(): void; -} -/** - * The inspector module provides an API for interacting with the V8 inspector. - */ -declare module 'node:inspector' { - import inspector = require('inspector'); - export = inspector; -} - -/** - * @types/node doesn't have a `node:inspector/promises` module, maybe because it's still experimental? - */ -declare module 'node:inspector/promises' { - /** - * Async Debugger session - */ - class Session { - constructor(); - - connect(): void; - - post(method: 'Debugger.pause' | 'Debugger.resume' | 'Debugger.enable' | 'Debugger.disable'): Promise; - post(method: 'Debugger.setPauseOnExceptions', params: Debugger.SetPauseOnExceptionsParameterType): Promise; - post( - method: 'Runtime.getProperties', - params: Runtime.GetPropertiesParameterType, - ): Promise; - - on( - event: 'Debugger.paused', - listener: (message: InspectorNotification) => void, - ): Session; - - on(event: 'Debugger.resumed', listener: () => void): Session; - } -} diff --git a/packages/node-experimental/src/integrations/local-variables/local-variables-async.ts b/packages/node-experimental/src/integrations/local-variables/local-variables-async.ts deleted file mode 100644 index baeeb4866be4..000000000000 --- a/packages/node-experimental/src/integrations/local-variables/local-variables-async.ts +++ /dev/null @@ -1,277 +0,0 @@ -import type { Session } from 'node:inspector/promises'; -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { Event, Exception, Integration, IntegrationClass, IntegrationFn, StackParser } from '@sentry/types'; -import { LRUMap, dynamicRequire, logger } from '@sentry/utils'; -import type { Debugger, InspectorNotification, Runtime } from 'inspector'; - -import type { NodeClient } from '../../sdk/client'; -import type { NodeClientOptions } from '../../types'; -import type { - FrameVariables, - LocalVariablesIntegrationOptions, - PausedExceptionEvent, - RateLimitIncrement, - Variables, -} from './common'; -import { createRateLimiter, functionNamesMatch, hashFrames, hashFromStack } from './common'; - -async function unrollArray(session: Session, objectId: string, name: string, vars: Variables): Promise { - const properties: Runtime.GetPropertiesReturnType = await session.post('Runtime.getProperties', { - objectId, - ownProperties: true, - }); - - vars[name] = properties.result - .filter(v => v.name !== 'length' && !isNaN(parseInt(v.name, 10))) - .sort((a, b) => parseInt(a.name, 10) - parseInt(b.name, 10)) - .map(v => v.value?.value); -} - -async function unrollObject(session: Session, objectId: string, name: string, vars: Variables): Promise { - const properties: Runtime.GetPropertiesReturnType = await session.post('Runtime.getProperties', { - objectId, - ownProperties: true, - }); - - vars[name] = properties.result - .map<[string, unknown]>(v => [v.name, v.value?.value]) - .reduce((obj, [key, val]) => { - obj[key] = val; - return obj; - }, {} as Variables); -} - -function unrollOther(prop: Runtime.PropertyDescriptor, vars: Variables): void { - if (!prop.value) { - return; - } - - if ('value' in prop.value) { - if (prop.value.value === undefined || prop.value.value === null) { - vars[prop.name] = `<${prop.value.value}>`; - } else { - vars[prop.name] = prop.value.value; - } - } else if ('description' in prop.value && prop.value.type !== 'function') { - vars[prop.name] = `<${prop.value.description}>`; - } else if (prop.value.type === 'undefined') { - vars[prop.name] = ''; - } -} - -async function getLocalVariables(session: Session, objectId: string): Promise { - const properties: Runtime.GetPropertiesReturnType = await session.post('Runtime.getProperties', { - objectId, - ownProperties: true, - }); - const variables = {}; - - for (const prop of properties.result) { - if (prop?.value?.objectId && prop?.value.className === 'Array') { - const id = prop.value.objectId; - await unrollArray(session, id, prop.name, variables); - } else if (prop?.value?.objectId && prop?.value?.className === 'Object') { - const id = prop.value.objectId; - await unrollObject(session, id, prop.name, variables); - } else if (prop?.value) { - unrollOther(prop, variables); - } - } - - return variables; -} - -const INTEGRATION_NAME = 'LocalVariablesAsync'; - -/** - * Adds local variables to exception frames - */ -const _localVariablesAsyncIntegration = ((options: LocalVariablesIntegrationOptions = {}) => { - const cachedFrames: LRUMap = new LRUMap(20); - let rateLimiter: RateLimitIncrement | undefined; - let shouldProcessEvent = false; - - async function handlePaused( - session: Session, - stackParser: StackParser, - { reason, data, callFrames }: PausedExceptionEvent, - ): Promise { - if (reason !== 'exception' && reason !== 'promiseRejection') { - return; - } - - rateLimiter?.(); - - // data.description contains the original error.stack - const exceptionHash = hashFromStack(stackParser, data?.description); - - if (exceptionHash == undefined) { - return; - } - - const frames = []; - - for (let i = 0; i < callFrames.length; i++) { - const { scopeChain, functionName, this: obj } = callFrames[i]; - - const localScope = scopeChain.find(scope => scope.type === 'local'); - - // obj.className is undefined in ESM modules - const fn = obj.className === 'global' || !obj.className ? functionName : `${obj.className}.${functionName}`; - - if (localScope?.object.objectId === undefined) { - frames[i] = { function: fn }; - } else { - const vars = await getLocalVariables(session, localScope.object.objectId); - frames[i] = { function: fn, vars }; - } - } - - cachedFrames.set(exceptionHash, frames); - } - - async function startDebugger(session: Session, clientOptions: NodeClientOptions): Promise { - session.connect(); - - let isPaused = false; - - session.on('Debugger.resumed', () => { - isPaused = false; - }); - - session.on('Debugger.paused', (event: InspectorNotification) => { - isPaused = true; - - handlePaused(session, clientOptions.stackParser, event.params as PausedExceptionEvent).then( - () => { - // After the pause work is complete, resume execution! - return isPaused ? session.post('Debugger.resume') : Promise.resolve(); - }, - _ => { - // ignore - }, - ); - }); - - await session.post('Debugger.enable'); - - const captureAll = options.captureAllExceptions !== false; - await session.post('Debugger.setPauseOnExceptions', { state: captureAll ? 'all' : 'uncaught' }); - - if (captureAll) { - const max = options.maxExceptionsPerSecond || 50; - - rateLimiter = createRateLimiter( - max, - () => { - logger.log('Local variables rate-limit lifted.'); - return session.post('Debugger.setPauseOnExceptions', { state: 'all' }); - }, - seconds => { - logger.log( - `Local variables rate-limit exceeded. Disabling capturing of caught exceptions for ${seconds} seconds.`, - ); - return session.post('Debugger.setPauseOnExceptions', { state: 'uncaught' }); - }, - ); - } - - shouldProcessEvent = true; - } - - function addLocalVariablesToException(exception: Exception): void { - const hash = hashFrames(exception.stacktrace?.frames); - - if (hash === undefined) { - return; - } - - // Check if we have local variables for an exception that matches the hash - // remove is identical to get but also removes the entry from the cache - const cachedFrame = cachedFrames.remove(hash); - - if (cachedFrame === undefined) { - return; - } - - const frameCount = exception.stacktrace?.frames?.length || 0; - - for (let i = 0; i < frameCount; i++) { - // Sentry frames are in reverse order - const frameIndex = frameCount - i - 1; - - // Drop out if we run out of frames to match up - if (!exception.stacktrace?.frames?.[frameIndex] || !cachedFrame[i]) { - break; - } - - if ( - // We need to have vars to add - cachedFrame[i].vars === undefined || - // We're not interested in frames that are not in_app because the vars are not relevant - exception.stacktrace.frames[frameIndex].in_app === false || - // The function names need to match - !functionNamesMatch(exception.stacktrace.frames[frameIndex].function, cachedFrame[i].function) - ) { - continue; - } - - exception.stacktrace.frames[frameIndex].vars = cachedFrame[i].vars; - } - } - - function addLocalVariablesToEvent(event: Event): Event { - for (const exception of event.exception?.values || []) { - addLocalVariablesToException(exception); - } - - return event; - } - - return { - name: INTEGRATION_NAME, - setup(client: NodeClient) { - const clientOptions = client.getOptions(); - - if (!clientOptions.includeLocalVariables) { - return; - } - - try { - // TODO: Use import()... - // It would be nice to use import() here, but this built-in library is not in Node <19 so webpack will pick it - // up and report it as a missing dependency - const { Session } = dynamicRequire(module, 'node:inspector/promises'); - - startDebugger(new Session(), clientOptions).catch(e => { - logger.error('Failed to start inspector session', e); - }); - } catch (e) { - logger.error('Failed to load inspector API', e); - return; - } - }, - processEvent(event: Event): Event { - if (shouldProcessEvent) { - return addLocalVariablesToEvent(event); - } - - return event; - }, - }; -}) satisfies IntegrationFn; - -export const localVariablesAsyncIntegration = defineIntegration(_localVariablesAsyncIntegration); - -/** - * Adds local variables to exception frames. - * @deprecated Use `localVariablesAsyncIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const LocalVariablesAsync = convertIntegrationFnToClass( - INTEGRATION_NAME, - localVariablesAsyncIntegration, -) as IntegrationClass Event; setup: (client: NodeClient) => void }>; - -// eslint-disable-next-line deprecation/deprecation -export type LocalVariablesAsync = typeof LocalVariablesAsync; diff --git a/packages/node-experimental/src/integrations/local-variables/local-variables-sync.ts b/packages/node-experimental/src/integrations/local-variables/local-variables-sync.ts index 1b8d3c356187..d17cb3282b81 100644 --- a/packages/node-experimental/src/integrations/local-variables/local-variables-sync.ts +++ b/packages/node-experimental/src/integrations/local-variables/local-variables-sync.ts @@ -1,7 +1,8 @@ import { convertIntegrationFnToClass, defineIntegration, getClient } from '@sentry/core'; import type { Event, Exception, Integration, IntegrationClass, IntegrationFn, StackParser } from '@sentry/types'; import { LRUMap, logger } from '@sentry/utils'; -import type { Debugger, InspectorNotification, Runtime, Session } from 'inspector'; +import type { Debugger, InspectorNotification, Runtime } from 'inspector'; +import { Session } from 'inspector'; import { NODE_MAJOR } from '../../nodeVersion'; import type { NodeClient } from '../../sdk/client'; @@ -78,22 +79,6 @@ class AsyncSession implements DebugSession { /** Throws if inspector API is not available */ public constructor() { - /* - TODO: We really should get rid of this require statement below for a couple of reasons: - 1. It makes the integration unusable in the SvelteKit SDK, as it's not possible to use `require` - in SvelteKit server code (at least not by default). - 2. Throwing in a constructor is bad practice - - More context for a future attempt to fix this: - We already tried replacing it with import but didn't get it to work because of async problems. - We still called import in the constructor but assigned to a promise which we "awaited" in - `configureAndConnect`. However, this broke the Node integration tests as no local variables - were reported any more. We probably missed a place where we need to await the promise, too. - */ - - // Node can be built without inspector support so this can throw - // eslint-disable-next-line @typescript-eslint/no-var-requires - const { Session } = require('inspector'); this._session = new Session(); } diff --git a/packages/node-experimental/src/integrations/tracing/hapi/index.ts b/packages/node-experimental/src/integrations/tracing/hapi/index.ts index 4d72af191f84..ac3766aaae86 100644 --- a/packages/node-experimental/src/integrations/tracing/hapi/index.ts +++ b/packages/node-experimental/src/integrations/tracing/hapi/index.ts @@ -1,6 +1,13 @@ import { registerInstrumentations } from '@opentelemetry/instrumentation'; import { HapiInstrumentation } from '@opentelemetry/instrumentation-hapi'; -import { SDK_VERSION, captureException, defineIntegration, getActiveSpan, getRootSpan } from '@sentry/core'; +import { + SDK_VERSION, + SPAN_STATUS_ERROR, + captureException, + defineIntegration, + getActiveSpan, + getRootSpan, +} from '@sentry/core'; import type { IntegrationFn } from '@sentry/types'; import type { Boom, RequestEvent, ResponseObject, Server } from './types'; @@ -61,7 +68,7 @@ export const hapiErrorPlugin = { } if (rootSpan) { - rootSpan.setStatus('internal_error'); + rootSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); rootSpan.end(); } }); diff --git a/packages/node-experimental/src/sdk/init.ts b/packages/node-experimental/src/sdk/init.ts index 80b69112f261..5e62a01de6cd 100644 --- a/packages/node-experimental/src/sdk/init.ts +++ b/packages/node-experimental/src/sdk/init.ts @@ -11,7 +11,7 @@ import { requestDataIntegration, startSession, } from '@sentry/core'; -import { setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry'; +import { openTelemetrySetupCheck, setOpenTelemetryContextAsyncContextStrategy } from '@sentry/opentelemetry'; import type { Client, Integration, Options } from '@sentry/types'; import { consoleSandbox, @@ -117,8 +117,36 @@ export function init(options: NodeOptions | undefined = {}): void { ); } - // Always init Otel, even if tracing is disabled, because we need it for trace propagation & the HTTP integration - initOtel(); + // If users opt-out of this, they _have_ to set up OpenTelemetry themselves + // There is no way to use this SDK without OpenTelemetry! + if (!options.skipOpenTelemetrySetup) { + initOtel(); + } + + validateOpenTelemetrySetup(); +} + +function validateOpenTelemetrySetup(): void { + if (!DEBUG_BUILD) { + return; + } + + const setup = openTelemetrySetupCheck(); + + const required = ['SentrySpanProcessor', 'SentryContextManager', 'SentryPropagator'] as const; + for (const k of required) { + if (!setup.includes(k)) { + logger.error( + `You have to set up the ${k}. Without this, the OpenTelemetry & Sentry integration will not work properly.`, + ); + } + } + + if (!setup.includes('SentrySampler')) { + logger.warn( + 'You have to set up the SentrySampler. Without this, the OpenTelemetry & Sentry integration may still work, but sample rates set for the Sentry SDK will not be respected.', + ); + } } function getClientOptions(options: NodeOptions): NodeClientOptions { diff --git a/packages/node-experimental/src/types.ts b/packages/node-experimental/src/types.ts index 9ac45c54b6ae..19a825e176f4 100644 --- a/packages/node-experimental/src/types.ts +++ b/packages/node-experimental/src/types.ts @@ -64,6 +64,16 @@ export interface BaseNodeOptions { */ spotlight?: boolean | string; + /** + * If this is set to true, the SDK will not set up OpenTelemetry automatically. + * In this case, you _have_ to ensure to set it up correctly yourself, including: + * * The `SentrySpanProcessor` + * * The `SentryPropagator` + * * The `SentryContextManager` + * * The `SentrySampler` + */ + skipOpenTelemetrySetup?: boolean; + /** Callback that is executed when a fatal global error occurs. */ onFatalError?(this: void, error: Error): void; } diff --git a/packages/node-experimental/test/integration/scope.test.ts b/packages/node-experimental/test/integration/scope.test.ts index ed5815e2df40..19848c4c78fc 100644 --- a/packages/node-experimental/test/integration/scope.test.ts +++ b/packages/node-experimental/test/integration/scope.test.ts @@ -82,8 +82,8 @@ describe('Integration | Scope', () => { tag2: 'val2', tag3: 'val3', tag4: 'val4', - ...(enableTracing ? { transaction: 'outer' } : {}), }, + ...(enableTracing ? { transaction: 'outer' } : {}), }), { event_id: expect.any(String), @@ -118,7 +118,6 @@ describe('Integration | Scope', () => { tag2: 'val2', tag3: 'val3', tag4: 'val4', - transaction: 'outer', }, timestamp: expect.any(Number), transaction: 'outer', @@ -203,8 +202,8 @@ describe('Integration | Scope', () => { tag2: 'val2a', tag3: 'val3a', tag4: 'val4a', - ...(enableTracing ? { transaction: 'outer' } : {}), }, + ...(enableTracing ? { transaction: 'outer' } : {}), }), { event_id: expect.any(String), @@ -229,8 +228,8 @@ describe('Integration | Scope', () => { tag2: 'val2b', tag3: 'val3b', tag4: 'val4b', - ...(enableTracing ? { transaction: 'outer' } : {}), }, + ...(enableTracing ? { transaction: 'outer' } : {}), }), { event_id: expect.any(String), diff --git a/packages/node-experimental/test/integration/transactions.test.ts b/packages/node-experimental/test/integration/transactions.test.ts index 05a7f1840ec1..aedde492cd63 100644 --- a/packages/node-experimental/test/integration/transactions.test.ts +++ b/packages/node-experimental/test/integration/transactions.test.ts @@ -1,8 +1,8 @@ import { TraceFlags, context, trace } from '@opentelemetry/api'; import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, spanToJSON } from '@sentry/core'; -import { SentrySpanProcessor, setPropagationContextOnContext } from '@sentry/opentelemetry'; -import type { PropagationContext, TransactionEvent } from '@sentry/types'; +import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; +import { SentrySpanProcessor } from '@sentry/opentelemetry'; +import type { TransactionEvent } from '@sentry/types'; import { logger } from '@sentry/utils'; import * as Sentry from '../../src'; @@ -124,7 +124,6 @@ describe('Integration | Transactions', () => { tags: { 'outer.tag': 'test value', 'test.tag': 'test value', - transaction: 'test name', }, timestamp: expect.any(Number), transaction: 'test name', @@ -142,7 +141,7 @@ describe('Integration | Transactions', () => { // note: Currently, spans do not have any context/span added to them // This is the same behavior as for the "regular" SDKs - expect(spans.map(span => spanToJSON(span))).toEqual([ + expect(spans).toEqual([ { data: { 'otel.kind': 'INTERNAL', @@ -267,7 +266,9 @@ describe('Integration | Transactions', () => { }), spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), - tags: { 'test.tag': 'test value', transaction: 'test name' }, + tags: { + 'test.tag': 'test value', + }, timestamp: expect.any(Number), transaction: 'test name', transaction_info: { source: 'task' }, @@ -310,7 +311,9 @@ describe('Integration | Transactions', () => { }), spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), - tags: { 'test.tag': 'test value b', transaction: 'test name b' }, + tags: { + 'test.tag': 'test value b', + }, timestamp: expect.any(Number), transaction: 'test name b', transaction_info: { source: 'custom' }, @@ -417,7 +420,9 @@ describe('Integration | Transactions', () => { }), spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), - tags: { 'test.tag': 'test value', transaction: 'test name' }, + tags: { + 'test.tag': 'test value', + }, timestamp: expect.any(Number), transaction: 'test name', type: 'transaction', @@ -456,7 +461,9 @@ describe('Integration | Transactions', () => { }), spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), - tags: { 'test.tag': 'test value b', transaction: 'test name b' }, + tags: { + 'test.tag': 'test value b', + }, timestamp: expect.any(Number), transaction: 'test name b', type: 'transaction', @@ -481,37 +488,27 @@ describe('Integration | Transactions', () => { traceFlags: TraceFlags.SAMPLED, }; - const propagationContext: PropagationContext = { - traceId, - parentSpanId, - spanId: '6e0c63257de34c93', - sampled: true, - }; - mockSdkInit({ enableTracing: true, beforeSendTransaction }); const client = Sentry.getClient()!; // We simulate the correct context we'd normally get from the SentryPropagator - context.with( - trace.setSpanContext(setPropagationContextOnContext(context.active(), propagationContext), spanContext), - () => { - Sentry.startSpan( - { - op: 'test op', - name: 'test name', - origin: 'auto.test', - attributes: { [Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task' }, - }, - () => { - const subSpan = Sentry.startInactiveSpan({ name: 'inner span 1' }); - subSpan.end(); + context.with(trace.setSpanContext(context.active(), spanContext), () => { + Sentry.startSpan( + { + op: 'test op', + name: 'test name', + origin: 'auto.test', + attributes: { [Sentry.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'task' }, + }, + () => { + const subSpan = Sentry.startInactiveSpan({ name: 'inner span 1' }); + subSpan.end(); - Sentry.startSpan({ name: 'inner span 2' }, () => {}); - }, - ); - }, - ); + Sentry.startSpan({ name: 'inner span 2' }, () => {}); + }, + ); + }); await client.flush(); @@ -562,7 +559,7 @@ describe('Integration | Transactions', () => { // note: Currently, spans do not have any context/span added to them // This is the same behavior as for the "regular" SDKs - expect(spans.map(span => spanToJSON(span))).toEqual([ + expect(spans).toEqual([ { data: { 'otel.kind': 'INTERNAL', diff --git a/packages/node-experimental/test/sdk/init.test.ts b/packages/node-experimental/test/sdk/init.test.ts index 7f0c3a8d71ec..d6e9bc7ee81d 100644 --- a/packages/node-experimental/test/sdk/init.test.ts +++ b/packages/node-experimental/test/sdk/init.test.ts @@ -2,6 +2,7 @@ import type { Integration } from '@sentry/types'; import * as auto from '../../src/integrations/tracing'; import { getClient } from '../../src/sdk/api'; +import type { NodeClient } from '../../src/sdk/client'; import { init } from '../../src/sdk/init'; import { cleanupOtel } from '../helpers/mockSdkInit'; @@ -119,4 +120,20 @@ describe('init()', () => { }), ); }); + + it('sets up OpenTelemetry by default', () => { + init({ dsn: PUBLIC_DSN }); + + const client = getClient(); + + expect(client.traceProvider).toBeDefined(); + }); + + it('allows to opt-out of OpenTelemetry setup', () => { + init({ dsn: PUBLIC_DSN, skipOpenTelemetrySetup: true }); + + const client = getClient(); + + expect(client.traceProvider).not.toBeDefined(); + }); }); diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index 8f4e1a9ee87c..571cef862d9c 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -86,8 +86,6 @@ export { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, } from '@sentry/core'; -export type { SpanStatusType } from '@sentry/core'; - export { autoDiscoverNodePerformanceMonitoringIntegrations } from './tracing'; export { NodeClient } from './client'; @@ -102,16 +100,12 @@ export { addRequestDataToEvent, DEFAULT_USER_INCLUDES, extractRequestData } from export { createGetModuleFromFilename } from './module'; -import { Integrations as CoreIntegrations } from '@sentry/core'; - import * as Handlers from './handlers'; import * as NodeIntegrations from './integrations'; import * as TracingIntegrations from './tracing/integrations'; // TODO: Deprecate this once we migrated tracing integrations export const Integrations = { - // eslint-disable-next-line deprecation/deprecation - ...CoreIntegrations, ...NodeIntegrations, ...TracingIntegrations, }; diff --git a/packages/node/src/integrations/contextlines.ts b/packages/node/src/integrations/contextlines.ts index 2f292a5606dc..7c367f51a970 100644 --- a/packages/node/src/integrations/contextlines.ts +++ b/packages/node/src/integrations/contextlines.ts @@ -1,6 +1,6 @@ import { readFile } from 'fs'; -import { convertIntegrationFnToClass, defineIntegration } from '@sentry/core'; -import type { Event, Integration, IntegrationClass, IntegrationFn, StackFrame } from '@sentry/types'; +import { defineIntegration } from '@sentry/core'; +import type { Event, IntegrationFn, StackFrame } from '@sentry/types'; import { LRUMap, addContextToFrame } from '@sentry/utils'; const FILE_CONTENT_CACHE = new LRUMap(100); @@ -48,15 +48,6 @@ const _contextLinesIntegration = ((options: ContextLinesOptions = {}) => { export const contextLinesIntegration = defineIntegration(_contextLinesIntegration); -/** - * Add node modules / packages to the event. - * @deprecated Use `contextLinesIntegration()` instead. - */ -// eslint-disable-next-line deprecation/deprecation -export const ContextLines = convertIntegrationFnToClass(INTEGRATION_NAME, contextLinesIntegration) as IntegrationClass< - Integration & { processEvent: (event: Event) => Promise } -> & { new (options?: { frameContextLines?: number }): Integration }; - async function addSourceContext(event: Event, contextLines: number): Promise { // keep a lookup map of which files we've already enqueued to read, // so we don't enqueue the same file multiple times which would cause multiple i/o reads @@ -122,9 +113,6 @@ function addSourceContextToFrames(frames: StackFrame[], contextLines: number): v } } -// eslint-disable-next-line deprecation/deprecation -export type ContextLines = typeof ContextLines; - /** * Reads file contents and caches them in a global LRU cache. * If reading fails, mark the file as null in the cache so we don't try again. diff --git a/packages/node/src/integrations/hapi/index.ts b/packages/node/src/integrations/hapi/index.ts index 9ea2df9e696c..3af732ce730a 100644 --- a/packages/node/src/integrations/hapi/index.ts +++ b/packages/node/src/integrations/hapi/index.ts @@ -1,5 +1,6 @@ import { SDK_VERSION, + SPAN_STATUS_ERROR, captureException, continueTrace, convertIntegrationFnToClass, @@ -58,7 +59,7 @@ export const hapiErrorPlugin = { } if (transaction) { - transaction.setStatus('internal_error'); + transaction.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); transaction.end(); } }); diff --git a/packages/node/src/integrations/index.ts b/packages/node/src/integrations/index.ts index 083df21bd68d..42cf53827542 100644 --- a/packages/node/src/integrations/index.ts +++ b/packages/node/src/integrations/index.ts @@ -4,7 +4,6 @@ export { Http } from './http'; export { OnUncaughtException } from './onuncaughtexception'; export { OnUnhandledRejection } from './onunhandledrejection'; export { Modules } from './modules'; -export { ContextLines } from './contextlines'; export { Context } from './context'; export { RequestData } from '@sentry/core'; export { Undici } from './undici'; diff --git a/packages/node/src/integrations/undici/index.ts b/packages/node/src/integrations/undici/index.ts index 222c75f852d8..5c27841c462d 100644 --- a/packages/node/src/integrations/undici/index.ts +++ b/packages/node/src/integrations/undici/index.ts @@ -1,5 +1,6 @@ import type { SentrySpan } from '@sentry/core'; import { + SPAN_STATUS_ERROR, addBreadcrumb, defineIntegration, getActiveSpan, @@ -278,7 +279,7 @@ export class Undici implements Integration { const span = request.__sentry_span__; if (span) { - span.setStatus('internal_error'); + span.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' }); span.end(); } diff --git a/packages/node/test/handlers.test.ts b/packages/node/test/handlers.test.ts index 3e65922faae1..861cfdf13259 100644 --- a/packages/node/test/handlers.test.ts +++ b/packages/node/test/handlers.test.ts @@ -487,7 +487,7 @@ describe('tracingHandler', () => { expect(finishTransaction).toHaveBeenCalled(); expect(spanToJSON(span).timestamp).toBeLessThanOrEqual(spanToJSON(transaction).timestamp!); expect(sentEvent.spans?.length).toEqual(1); - expect(sentEvent.spans?.[0].spanContext().spanId).toEqual(span.spanContext().spanId); + expect(sentEvent.spans?.[0].span_id).toEqual(span.spanContext().spanId); done(); }); }); diff --git a/packages/node/test/index.test.ts b/packages/node/test/index.test.ts index 563fa8886420..c6fc8a611aee 100644 --- a/packages/node/test/index.test.ts +++ b/packages/node/test/index.test.ts @@ -1,5 +1,4 @@ import { - LinkedErrors, SDK_VERSION, getGlobalScope, getIsolationScope, @@ -11,7 +10,7 @@ import { import type { EventHint, Integration } from '@sentry/types'; import type { Event } from '../src'; -import { contextLinesIntegration } from '../src'; +import { contextLinesIntegration, linkedErrorsIntegration } from '../src'; import { NodeClient, addBreadcrumb, @@ -24,7 +23,6 @@ import { init, } from '../src'; import { setNodeAsyncContextStrategy } from '../src/async'; -import { ContextLines } from '../src/integrations'; import { defaultStackParser, getDefaultIntegrations } from '../src/sdk'; import { getDefaultNodeClientOptions } from './helper/node-client-options'; @@ -217,8 +215,7 @@ describe('SentryNode', () => { expect.assertions(15); const options = getDefaultNodeClientOptions({ stackParser: defaultStackParser, - // eslint-disable-next-line deprecation/deprecation - integrations: [new ContextLines(), new LinkedErrors()], + integrations: [contextLinesIntegration(), linkedErrorsIntegration()], beforeSend: (event: Event) => { expect(event.exception).not.toBeUndefined(); expect(event.exception!.values![1]).not.toBeUndefined(); diff --git a/packages/node/test/integrations/contextlines.test.ts b/packages/node/test/integrations/contextlines.test.ts index dccc6c113b5f..dcc50f34c9a4 100644 --- a/packages/node/test/integrations/contextlines.test.ts +++ b/packages/node/test/integrations/contextlines.test.ts @@ -1,23 +1,25 @@ import * as fs from 'fs'; -import type { Event, Integration, StackFrame } from '@sentry/types'; +import type { Event, IntegrationFnResult, StackFrame } from '@sentry/types'; import { parseStackFrames } from '@sentry/utils'; -import { ContextLines, resetFileContentCache } from '../../src/integrations/contextlines'; +import { contextLinesIntegration } from '../../src'; +import { resetFileContentCache } from '../../src/integrations/contextlines'; import { defaultStackParser } from '../../src/sdk'; import { getError } from '../helper/error'; describe('ContextLines', () => { let readFileSpy: jest.SpyInstance; - let contextLines: Integration & { processEvent: (event: Event) => Promise }; + let contextLines: IntegrationFnResult; async function addContext(frames: StackFrame[]): Promise { - await contextLines.processEvent({ exception: { values: [{ stacktrace: { frames } }] } }); + await (contextLines as IntegrationFnResult & { processEvent: (event: Event) => Promise }).processEvent({ + exception: { values: [{ stacktrace: { frames } }] }, + }); } beforeEach(() => { readFileSpy = jest.spyOn(fs, 'readFile'); - // eslint-disable-next-line deprecation/deprecation - contextLines = new ContextLines(); + contextLines = contextLinesIntegration(); resetFileContentCache(); }); @@ -100,7 +102,7 @@ describe('ContextLines', () => { test('parseStack with no context', async () => { // eslint-disable-next-line deprecation/deprecation - contextLines = new ContextLines({ frameContextLines: 0 }); + contextLines = contextLinesIntegration({ frameContextLines: 0 }); expect.assertions(1); const frames = parseStackFrames(defaultStackParser, new Error('test')); @@ -113,7 +115,7 @@ describe('ContextLines', () => { test('does not attempt to readfile multiple times if it fails', async () => { expect.assertions(1); // eslint-disable-next-line deprecation/deprecation - contextLines = new ContextLines(); + contextLines = contextLinesIntegration(); readFileSpy.mockImplementation(() => { throw new Error("ENOENT: no such file or directory, open '/does/not/exist.js'"); diff --git a/packages/node/test/performance.test.ts b/packages/node/test/performance.test.ts index 3ae10bb1f4a3..0d528e37f7e2 100644 --- a/packages/node/test/performance.test.ts +++ b/packages/node/test/performance.test.ts @@ -1,7 +1,6 @@ import { setAsyncContextStrategy, setCurrentClient, - spanToJSON, startInactiveSpan, startSpan, startSpanManual, @@ -82,7 +81,7 @@ describe('startSpan()', () => { const transactionEvent = await transactionEventPromise; - expect(spanToJSON(transactionEvent.spans?.[0] as any).description).toBe('second'); + expect(transactionEvent.spans?.[0].description).toBe('second'); }); }); @@ -154,7 +153,7 @@ describe('startSpanManual()', () => { const transactionEvent = await transactionEventPromise; - expect(spanToJSON(transactionEvent.spans?.[0] as any).description).toBe('second'); + expect(transactionEvent.spans?.[0].description).toBe('second'); }); it('should use the scopes at time of creation instead of the scopes at time of termination', async () => { diff --git a/packages/opentelemetry-node/.eslintrc.js b/packages/opentelemetry-node/.eslintrc.js deleted file mode 100644 index 9899ea1b73d8..000000000000 --- a/packages/opentelemetry-node/.eslintrc.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - env: { - node: true, - }, - extends: ['../../.eslintrc.js'], - rules: { - '@sentry-internal/sdk/no-optional-chaining': 'off', - }, -}; diff --git a/packages/opentelemetry-node/LICENSE b/packages/opentelemetry-node/LICENSE deleted file mode 100644 index 4ac873d49f33..000000000000 --- a/packages/opentelemetry-node/LICENSE +++ /dev/null @@ -1,14 +0,0 @@ -Copyright (c) 2022 Sentry (https://sentry.io) and individual contributors. All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated -documentation files (the "Software"), to deal in the Software without restriction, including without limitation the -rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit -persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the -Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE -WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packages/opentelemetry-node/README.md b/packages/opentelemetry-node/README.md deleted file mode 100644 index 5d09f0edb182..000000000000 --- a/packages/opentelemetry-node/README.md +++ /dev/null @@ -1,75 +0,0 @@ -

- - Sentry - -

- -# Official Sentry SDK for OpenTelemetry Node - -[![npm version](https://img.shields.io/npm/v/@sentry/opentelemetry-node.svg)](https://www.npmjs.com/package/@sentry/opentelemetry-node) -[![npm dm](https://img.shields.io/npm/dm/@sentry/opentelemetry-node.svg)](https://www.npmjs.com/package/@sentry/opentelemetry-node) -[![npm dt](https://img.shields.io/npm/dt/@sentry/opentelemetry-node.svg)](https://www.npmjs.com/package/@sentry/opentelemetry-node) - -This package allows you to send your NodeJS OpenTelemetry trace data to Sentry via OpenTelemetry SpanProcessors. - -This SDK is **considered experimental and in an alpha state**. It may experience breaking changes. Please reach out on -[GitHub](https://github.com/getsentry/sentry-javascript/issues/new/choose) if you have any feedback/concerns. - -## Installation - -```bash -npm install @sentry/node @sentry/opentelemetry-node - -# Or yarn -yarn add @sentry/node @sentry/opentelemetry-node -``` - -Note that `@sentry/opentelemetry-node` depends on the following peer dependencies: - -- `@opentelemetry/api` version `1.0.0` or greater -- `@opentelemetry/sdk-trace-base` version `1.0.0` or greater, or a package that implements that, like - `@opentelemetry/sdk-node`. - -## Usage - -You need to register the `SentrySpanProcessor` and `SentryPropagator` with your OpenTelemetry installation: - -```js -const Sentry = require("@sentry/node"); -const { - SentrySpanProcessor, - SentryPropagator, -} = require("@sentry/opentelemetry-node"); - -const opentelemetry = require("@opentelemetry/sdk-node"); -const otelApi = require("@opentelemetry/api"); -const { - getNodeAutoInstrumentations, -} = require("@opentelemetry/auto-instrumentations-node"); -const { - OTLPTraceExporter, -} = require("@opentelemetry/exporter-trace-otlp-grpc"); - -// Make sure to call `Sentry.init` BEFORE initializing the OpenTelemetry SDK -Sentry.init({ - dsn: '__DSN__', - tracesSampleRate: 1.0, - // ... -}); - -const sdk = new opentelemetry.NodeSDK({ - // Existing config - traceExporter: new OTLPTraceExporter(), - instrumentations: [getNodeAutoInstrumentations()], - - // Sentry config - spanProcessor: new SentrySpanProcessor(), - textMapPropagator: new SentryPropagator(), -}); - -sdk.start(); -``` - -## Links - -- [Official SDK Docs](https://docs.sentry.io/quickstart/) diff --git a/packages/opentelemetry-node/jest.config.js b/packages/opentelemetry-node/jest.config.js deleted file mode 100644 index 24f49ab59a4c..000000000000 --- a/packages/opentelemetry-node/jest.config.js +++ /dev/null @@ -1 +0,0 @@ -module.exports = require('../../jest/jest.config.js'); diff --git a/packages/opentelemetry-node/package.json b/packages/opentelemetry-node/package.json deleted file mode 100644 index 27f11e28933e..000000000000 --- a/packages/opentelemetry-node/package.json +++ /dev/null @@ -1,81 +0,0 @@ -{ - "name": "@sentry/opentelemetry-node", - "version": "8.0.0-alpha.0", - "description": "Official Sentry SDK for OpenTelemetry Node.js", - "repository": "git://github.com/getsentry/sentry-javascript.git", - "homepage": "https://github.com/getsentry/sentry-javascript/tree/master/packages/opentelemetry-node", - "author": "Sentry", - "license": "MIT", - "engines": { - "node": ">=14.8" - }, - "files": [ - "cjs", - "esm", - "types", - "types-ts3.8" - ], - "main": "build/cjs/index.js", - "module": "build/esm/index.js", - "types": "build/types/index.d.ts", - "typesVersions": { - "<4.9": { - "build/types/index.d.ts": [ - "build/types-ts3.8/index.d.ts" - ] - } - }, - "publishConfig": { - "access": "public" - }, - "dependencies": { - "@sentry/core": "8.0.0-alpha.0", - "@sentry/types": "8.0.0-alpha.0", - "@sentry/utils": "8.0.0-alpha.0" - }, - "peerDependencies": { - "@opentelemetry/api": "1.x", - "@opentelemetry/core": "1.x", - "@opentelemetry/sdk-trace-base": "1.x", - "@opentelemetry/semantic-conventions": "1.x" - }, - "devDependencies": { - "@opentelemetry/api": "^1.6.0", - "@opentelemetry/core": "^1.17.1", - "@opentelemetry/sdk-trace-base": "^1.17.1", - "@opentelemetry/sdk-trace-node": "^1.17.1", - "@opentelemetry/semantic-conventions": "^1.17.1", - "@sentry/node-experimental": "8.0.0-alpha.0" - }, - "scripts": { - "build": "run-p build:transpile build:types", - "build:dev": "yarn build", - "build:transpile": "rollup -c rollup.npm.config.mjs", - "build:types": "run-s build:types:core build:types:downlevel", - "build:types:core": "tsc -p tsconfig.types.json", - "build:types:downlevel": "yarn downlevel-dts build/types build/types-ts3.8 --to ts3.8", - "build:watch": "run-p build:transpile:watch build:types:watch", - "build:dev:watch": "yarn build:watch", - "build:transpile:watch": "rollup -c rollup.npm.config.mjs --watch", - "build:types:watch": "tsc -p tsconfig.types.json --watch", - "build:tarball": "ts-node ../../scripts/prepack.ts && npm pack ./build", - "circularDepCheck": "madge --circular src/index.ts", - "clean": "rimraf build coverage sentry-opentelemetry-node-*.tgz", - "fix": "eslint . --format stylish --fix", - "lint": "eslint . --format stylish", - "test": "yarn test:jest", - "test:jest": "jest", - "test:watch": "jest --watch", - "yalc:publish": "ts-node ../../scripts/prepack.ts && yalc publish build --push --sig" - }, - "volta": { - "extends": "../../package.json" - }, - "sideEffects": [ - "./cjs/index.js", - "./esm/index.js", - "./build/npm/cjs/index.js", - "./build/npm/esm/index.js", - "./src/index.ts" - ] -} diff --git a/packages/opentelemetry-node/rollup.npm.config.mjs b/packages/opentelemetry-node/rollup.npm.config.mjs deleted file mode 100644 index 84a06f2fb64a..000000000000 --- a/packages/opentelemetry-node/rollup.npm.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { makeBaseNPMConfig, makeNPMConfigVariants } from '@sentry-internal/rollup-utils'; - -export default makeNPMConfigVariants(makeBaseNPMConfig()); diff --git a/packages/opentelemetry-node/src/constants.ts b/packages/opentelemetry-node/src/constants.ts deleted file mode 100644 index 55f386f2b39f..000000000000 --- a/packages/opentelemetry-node/src/constants.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { createContextKey } from '@opentelemetry/api'; - -export const SENTRY_TRACE_HEADER = 'sentry-trace'; - -export const SENTRY_BAGGAGE_HEADER = 'baggage'; - -export const SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY = createContextKey('SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY'); - -export const SENTRY_TRACE_PARENT_CONTEXT_KEY = createContextKey('SENTRY_TRACE_PARENT_CONTEXT_KEY'); diff --git a/packages/opentelemetry-node/src/debug-build.ts b/packages/opentelemetry-node/src/debug-build.ts deleted file mode 100644 index 60aa50940582..000000000000 --- a/packages/opentelemetry-node/src/debug-build.ts +++ /dev/null @@ -1,8 +0,0 @@ -declare const __DEBUG_BUILD__: boolean; - -/** - * This serves as a build time flag that will be true by default, but false in non-debug builds or if users replace `__SENTRY_DEBUG__` in their generated code. - * - * ATTENTION: This constant must never cross package boundaries (i.e. be exported) to guarantee that it can be used for tree shaking. - */ -export const DEBUG_BUILD = __DEBUG_BUILD__; diff --git a/packages/opentelemetry-node/src/index.ts b/packages/opentelemetry-node/src/index.ts deleted file mode 100644 index 72f9bf46d5e9..000000000000 --- a/packages/opentelemetry-node/src/index.ts +++ /dev/null @@ -1,5 +0,0 @@ -export { SentrySpanProcessor } from './spanprocessor'; -export { SentryPropagator } from './propagator'; -export { maybeCaptureExceptionForTimedEvent } from './utils/captureExceptionForTimedEvent'; -export { parseOtelSpanDescription } from './utils/parseOtelSpanDescription'; -export { mapOtelStatus } from './utils/mapOtelStatus'; diff --git a/packages/opentelemetry-node/src/propagator.ts b/packages/opentelemetry-node/src/propagator.ts deleted file mode 100644 index ead43afdb8f5..000000000000 --- a/packages/opentelemetry-node/src/propagator.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { Baggage, Context, TextMapGetter, TextMapSetter } from '@opentelemetry/api'; -import { TraceFlags, isSpanContextValid, propagation, trace } from '@opentelemetry/api'; -import { W3CBaggagePropagator, isTracingSuppressed } from '@opentelemetry/core'; -import { getDynamicSamplingContextFromSpan, getRootSpan, spanToTraceHeader } from '@sentry/core'; -import { - SENTRY_BAGGAGE_KEY_PREFIX, - baggageHeaderToDynamicSamplingContext, - extractTraceparentData, -} from '@sentry/utils'; - -import { - SENTRY_BAGGAGE_HEADER, - SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, - SENTRY_TRACE_HEADER, - SENTRY_TRACE_PARENT_CONTEXT_KEY, -} from './constants'; -import { getSentrySpan } from './utils/spanMap'; - -/** - * Injects and extracts `sentry-trace` and `baggage` headers from carriers. - */ -export class SentryPropagator extends W3CBaggagePropagator { - /** - * @inheritDoc - */ - public inject(context: Context, carrier: unknown, setter: TextMapSetter): void { - const spanContext = trace.getSpanContext(context); - if (!spanContext || !isSpanContextValid(spanContext) || isTracingSuppressed(context)) { - return; - } - - let baggage = propagation.getBaggage(context) || propagation.createBaggage({}); - - const span = getSentrySpan(spanContext.spanId); - if (span) { - setter.set(carrier, SENTRY_TRACE_HEADER, spanToTraceHeader(span)); - - if (getRootSpan(span)) { - const dynamicSamplingContext = getDynamicSamplingContextFromSpan(span); - baggage = Object.entries(dynamicSamplingContext).reduce((b, [dscKey, dscValue]) => { - if (dscValue) { - return b.setEntry(`${SENTRY_BAGGAGE_KEY_PREFIX}${dscKey}`, { value: dscValue }); - } - return b; - }, baggage); - } - } - super.inject(propagation.setBaggage(context, baggage), carrier, setter); - } - - /** - * @inheritDoc - */ - public extract(context: Context, carrier: unknown, getter: TextMapGetter): Context { - let newContext = context; - - const maybeSentryTraceHeader: string | string[] | undefined = getter.get(carrier, SENTRY_TRACE_HEADER); - if (maybeSentryTraceHeader) { - const header = Array.isArray(maybeSentryTraceHeader) ? maybeSentryTraceHeader[0] : maybeSentryTraceHeader; - const traceparentData = extractTraceparentData(header || ''); - newContext = newContext.setValue(SENTRY_TRACE_PARENT_CONTEXT_KEY, traceparentData); - if (traceparentData) { - const spanContext = { - traceId: traceparentData.traceId || '', - spanId: traceparentData.parentSpanId || '', - isRemote: true, - // Always sample if traceparent exists, we use SentrySpanProcessor to make sampling decisions with `startTransaction`. - traceFlags: TraceFlags.SAMPLED, - }; - newContext = trace.setSpanContext(newContext, spanContext); - } - } - - const maybeBaggageHeader = getter.get(carrier, SENTRY_BAGGAGE_HEADER); - const dynamicSamplingContext = baggageHeaderToDynamicSamplingContext(maybeBaggageHeader); - newContext = newContext.setValue(SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, dynamicSamplingContext); - - return newContext; - } - - /** - * @inheritDoc - */ - public fields(): string[] { - return [SENTRY_TRACE_HEADER, SENTRY_BAGGAGE_HEADER]; - } -} diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts deleted file mode 100644 index 7788858c586d..000000000000 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ /dev/null @@ -1,234 +0,0 @@ -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, - Transaction, - addEventProcessor, - addTracingExtensions, - getClient, - getCurrentHub, -} from '@sentry/core'; -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'; -import { DEBUG_BUILD } from './debug-build'; -import { maybeCaptureExceptionForTimedEvent } from './utils/captureExceptionForTimedEvent'; -import { isSentryRequestSpan } from './utils/isSentryRequest'; -import { mapOtelStatus } from './utils/mapOtelStatus'; -import { parseOtelSpanDescription } from './utils/parseOtelSpanDescription'; -import { clearSpan, getSentrySpan, setSentrySpan } from './utils/spanMap'; - -/** - * Converts OpenTelemetry Spans to Sentry Spans and sends them to Sentry via - * the Sentry SDK. - */ -export class SentrySpanProcessor implements OtelSpanProcessor { - public constructor() { - addTracingExtensions(); - - addEventProcessor(event => { - const otelSpan = trace && trace.getActiveSpan && (trace.getActiveSpan() as OtelSpan | undefined); - if (!otelSpan) { - return event; - } - - const otelSpanContext = otelSpan.spanContext(); - - // If event has already set `trace` context, use that one. - event.contexts = { - trace: { - trace_id: otelSpanContext.traceId, - span_id: otelSpanContext.spanId, - parent_span_id: otelSpan.parentSpanId, - }, - ...event.contexts, - }; - - return event; - }); - } - - /** - * @inheritDoc - */ - public onStart(otelSpan: OtelSpan, parentContext: Context): void { - const otelSpanId = otelSpan.spanContext().spanId; - const otelParentSpanId = otelSpan.parentSpanId; - - // Otel supports having multiple non-nested spans at the same time - // so we cannot use hub.getSpan(), as we cannot rely on this being on the current span - const sentryParentSpan = otelParentSpanId && getSentrySpan(otelParentSpanId); - - if (sentryParentSpan) { - // eslint-disable-next-line deprecation/deprecation - const sentryChildSpan = sentryParentSpan.startChild({ - name: otelSpan.name, - startTimestamp: convertOtelTimeToSeconds(otelSpan.startTime), - spanId: otelSpanId, - }) as SentrySpan; - - setSentrySpan(otelSpanId, sentryChildSpan); - } else { - const traceCtx = getTraceData(otelSpan, parentContext); - // eslint-disable-next-line deprecation/deprecation - const transaction = getCurrentHub().startTransaction({ - name: otelSpan.name, - ...traceCtx, - attributes: otelSpan.attributes, - startTimestamp: convertOtelTimeToSeconds(otelSpan.startTime), - spanId: otelSpanId, - }); - - setSentrySpan(otelSpanId, transaction as unknown as SentrySpan); - } - } - - /** - * @inheritDoc - */ - public onEnd(otelSpan: OtelSpan): void { - const otelSpanId = otelSpan.spanContext().spanId; - const sentrySpan = getSentrySpan(otelSpanId); - - if (!sentrySpan) { - DEBUG_BUILD && logger.error(`SentrySpanProcessor could not find span with OTEL-spanId ${otelSpanId} to finish.`); - clearSpan(otelSpanId); - return; - } - - // Auto-instrumentation often captures outgoing HTTP requests - // This means that Sentry HTTP requests created by this integration can, in turn, be captured by OTEL auto instrumentation, - // leading to an infinite loop. - // In this case, we do not want to finish the span, in order to avoid sending it to Sentry - if (isSentryRequestSpan(otelSpan)) { - clearSpan(otelSpanId); - return; - } - - const client = getClient(); - - const mutableOptions = { drop: false }; - client && client.emit('otelSpanEnd', otelSpan, mutableOptions); - - if (mutableOptions.drop) { - clearSpan(otelSpanId); - return; - } - - // eslint-disable-next-line deprecation/deprecation - const hub = getCurrentHub(); - otelSpan.events.forEach(event => { - maybeCaptureExceptionForTimedEvent(hub, event, otelSpan); - }); - - if (sentrySpan instanceof Transaction) { - updateTransactionWithOtelData(sentrySpan, otelSpan); - sentrySpan.setHub(hub); - } else { - updateSpanWithOtelData(sentrySpan, otelSpan); - } - - // Ensure we do not capture any OTEL spans for finishing (and sending) this - context.with(suppressTracing(context.active()), () => { - sentrySpan.end(convertOtelTimeToSeconds(otelSpan.endTime)); - }); - - clearSpan(otelSpanId); - } - - /** - * @inheritDoc - */ - public shutdown(): Promise { - return Promise.resolve(); - } - - /** - * @inheritDoc - */ - public async forceFlush(): Promise { - const client = getClient(); - if (client) { - return client.flush().then(); - } - return Promise.resolve(); - } -} - -function getTraceData(otelSpan: OtelSpan, parentContext: Context): Partial { - const spanContext = otelSpan.spanContext(); - const traceId = spanContext.traceId; - const spanId = spanContext.spanId; - - const parentSpanId = otelSpan.parentSpanId; - const traceparentData = parentContext.getValue(SENTRY_TRACE_PARENT_CONTEXT_KEY) as TraceparentData | undefined; - const dynamicSamplingContext = parentContext.getValue(SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY) as - | Partial - | undefined; - - const context: Partial = { - spanId, - traceId, - parentSpanId, - metadata: { - // only set dynamic sampling context if sentry-trace header was set - dynamicSamplingContext: traceparentData && !dynamicSamplingContext ? {} : dynamicSamplingContext, - }, - attributes: { - [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom', - }, - }; - - // Only inherit sample rate if `traceId` is the same - if (traceparentData && traceId === traceparentData.traceId) { - context.parentSampled = traceparentData.parentSampled; - } - - return context; -} - -function updateSpanWithOtelData(sentrySpan: SentrySpan, otelSpan: OtelSpan): void { - const { attributes, kind } = otelSpan; - - const { op, description, data } = parseOtelSpanDescription(otelSpan); - - sentrySpan.setStatus(mapOtelStatus(otelSpan)); - - const allData = { - ...attributes, - ...data, - 'otel.kind': SpanKind[kind], - }; - sentrySpan.setAttributes(allData); - - sentrySpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, op); - sentrySpan.updateName(description); -} - -function updateTransactionWithOtelData(transaction: Transaction, otelSpan: OtelSpan): void { - const { op, description, source, data } = parseOtelSpanDescription(otelSpan); - - // eslint-disable-next-line deprecation/deprecation - transaction.setContext('otel', { - attributes: otelSpan.attributes, - resource: otelSpan.resource.attributes, - }); - - const allData = data || {}; - transaction.setAttributes(allData); - - transaction.setStatus(mapOtelStatus(otelSpan)); - - transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_OP, op); - transaction.updateName(description); - transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, source); -} - -function convertOtelTimeToSeconds([seconds, nano]: [number, number]): number { - return seconds + nano / 1_000_000_000; -} diff --git a/packages/opentelemetry-node/src/utils/captureExceptionForTimedEvent.ts b/packages/opentelemetry-node/src/utils/captureExceptionForTimedEvent.ts deleted file mode 100644 index dcf02b671044..000000000000 --- a/packages/opentelemetry-node/src/utils/captureExceptionForTimedEvent.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { Span as OtelSpan, TimedEvent } from '@opentelemetry/sdk-trace-base'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import type { Hub } from '@sentry/types'; -import { isString } from '@sentry/utils'; - -/** - * Maybe capture a Sentry exception for an OTEL timed event. - * This will check if the event is exception-like and in that case capture it as an exception. - */ -export function maybeCaptureExceptionForTimedEvent(hub: Hub, event: TimedEvent, otelSpan?: OtelSpan): void { - if (event.name !== 'exception') { - return; - } - - const attributes = event.attributes; - if (!attributes) { - return; - } - - const message = attributes[SemanticAttributes.EXCEPTION_MESSAGE]; - - if (typeof message !== 'string') { - return; - } - - const syntheticError = new Error(message); - - const stack = attributes[SemanticAttributes.EXCEPTION_STACKTRACE]; - if (isString(stack)) { - syntheticError.stack = stack; - } - - const type = attributes[SemanticAttributes.EXCEPTION_TYPE]; - if (isString(type)) { - syntheticError.name = type; - } - - // eslint-disable-next-line deprecation/deprecation - hub.captureException(syntheticError, { - captureContext: otelSpan - ? { - contexts: { - otel: { - attributes: otelSpan.attributes, - resource: otelSpan.resource.attributes, - }, - trace: { - trace_id: otelSpan.spanContext().traceId, - span_id: otelSpan.spanContext().spanId, - parent_span_id: otelSpan.parentSpanId, - }, - }, - } - : undefined, - }); -} diff --git a/packages/opentelemetry-node/src/utils/isSentryRequest.ts b/packages/opentelemetry-node/src/utils/isSentryRequest.ts deleted file mode 100644 index 85cb6c9c77b9..000000000000 --- a/packages/opentelemetry-node/src/utils/isSentryRequest.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import { getClient, isSentryRequestUrl } from '@sentry/core'; - -/** - * - * @param otelSpan Checks wheter a given OTEL Span is an http request to sentry. - * @returns boolean - */ -export function isSentryRequestSpan(otelSpan: OtelSpan): boolean { - const { attributes } = otelSpan; - - const httpUrl = attributes[SemanticAttributes.HTTP_URL]; - - if (!httpUrl) { - return false; - } - - return isSentryRequestUrl(httpUrl.toString(), getClient()); -} diff --git a/packages/opentelemetry-node/src/utils/mapOtelStatus.ts b/packages/opentelemetry-node/src/utils/mapOtelStatus.ts deleted file mode 100644 index f7fd6e68bb7c..000000000000 --- a/packages/opentelemetry-node/src/utils/mapOtelStatus.ts +++ /dev/null @@ -1,74 +0,0 @@ -import { SpanStatusCode } from '@opentelemetry/api'; -import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import type { SpanStatusType as SentryStatus } from '@sentry/core'; - -// canonicalCodesHTTPMap maps some HTTP codes to Sentry's span statuses. See possible mapping in https://develop.sentry.dev/sdk/event-payloads/span/ -const canonicalCodesHTTPMap: Record = { - '400': 'failed_precondition', - '401': 'unauthenticated', - '403': 'permission_denied', - '404': 'not_found', - '409': 'aborted', - '429': 'resource_exhausted', - '499': 'cancelled', - '500': 'internal_error', - '501': 'unimplemented', - '503': 'unavailable', - '504': 'deadline_exceeded', -} as const; - -// canonicalCodesGrpcMap maps some GRPC codes to Sentry's span statuses. See description in grpc documentation. -const canonicalCodesGrpcMap: Record = { - '1': 'cancelled', - '2': 'unknown_error', - '3': 'invalid_argument', - '4': 'deadline_exceeded', - '5': 'not_found', - '6': 'already_exists', - '7': 'permission_denied', - '8': 'resource_exhausted', - '9': 'failed_precondition', - '10': 'aborted', - '11': 'out_of_range', - '12': 'unimplemented', - '13': 'internal_error', - '14': 'unavailable', - '15': 'data_loss', - '16': 'unauthenticated', -} as const; - -/** - * Get a Sentry span status from an otel span. - * - * @param otelSpan An otel span to generate a sentry status for. - * @returns The Sentry span status - */ -export function mapOtelStatus(otelSpan: OtelSpan): SentryStatus { - const { status, attributes } = otelSpan; - - const httpCode = attributes[SemanticAttributes.HTTP_STATUS_CODE]; - const grpcCode = attributes[SemanticAttributes.RPC_GRPC_STATUS_CODE]; - - const code = typeof httpCode === 'string' ? httpCode : typeof httpCode === 'number' ? httpCode.toString() : undefined; - if (code) { - const sentryStatus = canonicalCodesHTTPMap[code]; - if (sentryStatus) { - return sentryStatus; - } - } - - if (typeof grpcCode === 'string') { - const sentryStatus = canonicalCodesGrpcMap[grpcCode]; - if (sentryStatus) { - return sentryStatus; - } - } - - const statusCode = status.code; - if (statusCode === SpanStatusCode.OK || statusCode === SpanStatusCode.UNSET) { - return 'ok'; - } - - return 'unknown_error'; -} diff --git a/packages/opentelemetry-node/src/utils/parseOtelSpanDescription.ts b/packages/opentelemetry-node/src/utils/parseOtelSpanDescription.ts deleted file mode 100644 index d53d31aa2069..000000000000 --- a/packages/opentelemetry-node/src/utils/parseOtelSpanDescription.ts +++ /dev/null @@ -1,165 +0,0 @@ -import type { AttributeValue, Attributes } from '@opentelemetry/api'; -import { SpanKind } from '@opentelemetry/api'; -import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import type { TransactionSource } from '@sentry/types'; -import { getSanitizedUrlString, parseUrl, stripUrlQueryAndFragment } from '@sentry/utils'; - -interface SpanDescription { - op: string | undefined; - description: string; - source: TransactionSource; - data?: Record; -} - -/** - * Extract better op/description from an otel span. - * - * Based on https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/7422ce2a06337f68a59b552b8c5a2ac125d6bae5/exporter/sentryexporter/sentry_exporter.go#L306 - * - * @param otelSpan - * @returns Better op/description to use, or undefined - */ -export function parseOtelSpanDescription(otelSpan: OtelSpan): SpanDescription { - const { attributes, name } = otelSpan; - - // if http.method exists, this is an http request span - const httpMethod = attributes[SemanticAttributes.HTTP_METHOD]; - if (httpMethod) { - return descriptionForHttpMethod(otelSpan, httpMethod); - } - - // If db.type exists then this is a database call span. - const dbSystem = attributes[SemanticAttributes.DB_SYSTEM]; - if (dbSystem) { - return descriptionForDbSystem(otelSpan, dbSystem); - } - - // If rpc.service exists then this is a rpc call span. - const rpcService = attributes[SemanticAttributes.RPC_SERVICE]; - if (rpcService) { - return { - op: 'rpc', - description: name, - source: 'route', - }; - } - - // If messaging.system exists then this is a messaging system span. - const messagingSystem = attributes[SemanticAttributes.MESSAGING_SYSTEM]; - if (messagingSystem) { - return { - op: 'message', - description: name, - source: 'route', - }; - } - - // If faas.trigger exists then this is a function as a service span. - const faasTrigger = attributes[SemanticAttributes.FAAS_TRIGGER]; - if (faasTrigger) { - return { op: faasTrigger.toString(), description: name, source: 'route' }; - } - - return { op: undefined, description: name, source: 'custom' }; -} - -function descriptionForDbSystem(otelSpan: OtelSpan, _dbSystem: AttributeValue): SpanDescription { - const { attributes, name } = otelSpan; - - // Use DB statement (Ex "SELECT * FROM table") if possible as description. - const statement = attributes[SemanticAttributes.DB_STATEMENT]; - - const description = statement ? statement.toString() : name; - - return { op: 'db', description, source: 'task' }; -} - -function descriptionForHttpMethod(otelSpan: OtelSpan, httpMethod: AttributeValue): SpanDescription { - const { name, kind, attributes } = otelSpan; - - const opParts = ['http']; - - switch (kind) { - case SpanKind.CLIENT: - opParts.push('client'); - break; - case SpanKind.SERVER: - opParts.push('server'); - break; - } - - const httpRoute = attributes[SemanticAttributes.HTTP_ROUTE]; - const { urlPath, url, query, fragment } = getSanitizedUrl(attributes, kind); - - if (!urlPath) { - return { op: opParts.join('.'), description: name, source: 'custom' }; - } - - // Ex. description="GET /api/users". - const description = `${httpMethod} ${urlPath}`; - - // If `httpPath` is a root path, then we can categorize the transaction source as route. - const source: TransactionSource = httpRoute || urlPath === '/' ? 'route' : 'url'; - - const data: Record = {}; - - if (url) { - data.url = url; - } - if (query) { - data['http.query'] = query; - } - if (fragment) { - data['http.fragment'] = fragment; - } - - return { - op: opParts.join('.'), - description, - source, - data, - }; -} - -/** Exported for tests only */ -export function getSanitizedUrl( - attributes: Attributes, - kind: SpanKind, -): { - url: string | undefined; - urlPath: string | undefined; - query: string | undefined; - fragment: string | undefined; -} { - // This is the relative path of the URL, e.g. /sub - const httpTarget = attributes[SemanticAttributes.HTTP_TARGET]; - // This is the full URL, including host & query params etc., e.g. https://example.com/sub?foo=bar - const httpUrl = attributes[SemanticAttributes.HTTP_URL]; - // This is the normalized route name - may not always be available! - const httpRoute = attributes[SemanticAttributes.HTTP_ROUTE]; - - const parsedUrl = typeof httpUrl === 'string' ? parseUrl(httpUrl) : undefined; - const url = parsedUrl ? getSanitizedUrlString(parsedUrl) : undefined; - const query = parsedUrl && parsedUrl.search ? parsedUrl.search : undefined; - const fragment = parsedUrl && parsedUrl.hash ? parsedUrl.hash : undefined; - - if (typeof httpRoute === 'string') { - return { urlPath: httpRoute, url, query, fragment }; - } - - if (kind === SpanKind.SERVER && typeof httpTarget === 'string') { - return { urlPath: stripUrlQueryAndFragment(httpTarget), url, query, fragment }; - } - - if (parsedUrl) { - return { urlPath: url, url, query, fragment }; - } - - // fall back to target even for client spans, if no URL is present - if (typeof httpTarget === 'string') { - return { urlPath: stripUrlQueryAndFragment(httpTarget), url, query, fragment }; - } - - return { urlPath: undefined, url, query, fragment }; -} diff --git a/packages/opentelemetry-node/src/utils/spanMap.ts b/packages/opentelemetry-node/src/utils/spanMap.ts deleted file mode 100644 index 49e4c033403e..000000000000 --- a/packages/opentelemetry-node/src/utils/spanMap.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { SentrySpan } from '@sentry/core'; -import { getRootSpan } from '@sentry/core'; - -interface SpanMapEntry { - sentrySpan: SentrySpan; - ref: SpanRefType; - // These are not direct children, but all spans under the tree of a root span. - subSpans: string[]; -} - -const SPAN_REF_ROOT = Symbol('root'); -const SPAN_REF_CHILD = Symbol('child'); -const SPAN_REF_CHILD_ENDED = Symbol('child_ended'); -type SpanRefType = typeof SPAN_REF_ROOT | typeof SPAN_REF_CHILD | typeof SPAN_REF_CHILD_ENDED; - -/** Exported only for tests. */ -export const SPAN_MAP = new Map(); - -/** - * Get a Sentry span for a given span ID. - */ -export function getSentrySpan(spanId: string): SentrySpan | undefined { - const entry = SPAN_MAP.get(spanId); - return entry ? entry.sentrySpan : undefined; -} - -/** - * Set a Sentry span for a given span ID. - * This is necessary so we can lookup parent spans later. - * We also keep a list of children for root spans only, in order to be able to clean them up together. - */ -export function setSentrySpan(spanId: string, sentrySpan: SentrySpan): void { - let ref: SpanRefType = SPAN_REF_ROOT; - - const rootSpanId = getRootSpan(sentrySpan)?.spanContext().spanId; - - if (rootSpanId && rootSpanId !== spanId) { - const root = SPAN_MAP.get(rootSpanId); - if (root) { - root.subSpans.push(spanId); - ref = SPAN_REF_CHILD; - } - } - - SPAN_MAP.set(spanId, { - sentrySpan, - ref, - subSpans: [], - }); -} - -/** - * Clear references of the given span ID. - */ -export function clearSpan(spanId: string): void { - const entry = SPAN_MAP.get(spanId); - if (!entry) { - return; - } - - const { ref, subSpans } = entry; - - // If this is a child, mark it as ended. - if (ref === SPAN_REF_CHILD) { - entry.ref = SPAN_REF_CHILD_ENDED; - return; - } - - // If this is a root span, clear all (ended) children - if (ref === SPAN_REF_ROOT) { - for (const childId of subSpans) { - const child = SPAN_MAP.get(childId); - if (!child) { - continue; - } - - if (child.ref === SPAN_REF_CHILD_ENDED) { - // if the child has already ended, just clear it - SPAN_MAP.delete(childId); - } else if (child.ref === SPAN_REF_CHILD) { - // If the child has not ended yet, mark it as a root span so it is cleared when it ends. - child.ref = SPAN_REF_ROOT; - } - } - - SPAN_MAP.delete(spanId); - return; - } - - // Generally, `clearSpan` should never be called for ref === SPAN_REF_CHILD_ENDED - // But if it does, just clear the span - SPAN_MAP.delete(spanId); -} diff --git a/packages/opentelemetry-node/test/propagator.test.ts b/packages/opentelemetry-node/test/propagator.test.ts deleted file mode 100644 index 550ec2633843..000000000000 --- a/packages/opentelemetry-node/test/propagator.test.ts +++ /dev/null @@ -1,263 +0,0 @@ -import { - ROOT_CONTEXT, - TraceFlags, - defaultTextMapGetter, - defaultTextMapSetter, - propagation, - 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'; - -import { - SENTRY_BAGGAGE_HEADER, - SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY, - SENTRY_TRACE_HEADER, - SENTRY_TRACE_PARENT_CONTEXT_KEY, -} from '../src/constants'; -import { SentryPropagator } from '../src/propagator'; -import { SPAN_MAP, setSentrySpan } from '../src/utils/spanMap'; - -beforeAll(() => { - addTracingExtensions(); -}); - -describe('SentryPropagator', () => { - const propagator = new SentryPropagator(); - let carrier: { [key: string]: unknown }; - - beforeEach(() => { - carrier = {}; - }); - - it('returns fields set', () => { - expect(propagator.fields()).toEqual([SENTRY_TRACE_HEADER, SENTRY_BAGGAGE_HEADER]); - }); - - describe('inject', () => { - describe('baggage and sentry-trace', () => { - const client = { - getOptions: () => ({ - environment: 'production', - release: '1.0.0', - }), - getDsn: () => ({ - publicKey: 'abc', - }), - emit: () => {}, - } as unknown as Client; - - setCurrentClient(client); - - afterEach(() => { - SPAN_MAP.clear(); - }); - - enum PerfType { - Transaction = 'transaction', - Span = 'span', - } - - function createTransactionAndMaybeSpan(type: PerfType, transactionContext: TransactionContext) { - // eslint-disable-next-line deprecation/deprecation - const transaction = new Transaction(transactionContext, getCurrentHub()); - setSentrySpan(transaction.spanContext().spanId, transaction); - if (type === PerfType.Span) { - // 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 }) as SentrySpan; - setSentrySpan(span.spanContext().spanId, span); - } - } - - describe.each([PerfType.Transaction, PerfType.Span])('with active %s', type => { - it.each([ - [ - 'should set baggage and header when sampled', - { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }, - { - name: 'sampled-transaction', - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - sampled: true, - }, - 'sentry-environment=production,sentry-release=1.0.0,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b,sentry-transaction=sampled-transaction,sentry-sampled=true', - 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1', - ], - [ - 'should NOT set baggage header when not sampled', - { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.NONE, - }, - { - name: 'not-sampled-transaction', - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - sampled: false, - }, - 'sentry-environment=production,sentry-release=1.0.0,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b,sentry-transaction=not-sampled-transaction,sentry-sampled=false', - 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-0', - ], - [ - 'should NOT set baggage header when traceId is empty', - { - traceId: '', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }, - { - name: 'empty-traceId-transaction', - traceId: '', - spanId: '6e0c63257de34c92', - sampled: true, - }, - undefined, - undefined, - ], - [ - 'should NOT set baggage header when spanId is empty', - { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '', - traceFlags: TraceFlags.SAMPLED, - }, - { - name: 'empty-spanId-transaction', - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '', - sampled: true, - }, - undefined, - undefined, - ], - ])('%s', (_name, spanContext, transactionContext, baggage, sentryTrace) => { - createTransactionAndMaybeSpan(type, transactionContext); - const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); - propagator.inject(context, carrier, defaultTextMapSetter); - expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(baggage); - expect(carrier[SENTRY_TRACE_HEADER]).toBe(sentryTrace); - }); - - it('should include existing baggage', () => { - const transactionContext = { - name: 'sampled-transaction', - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - sampled: true, - }; - const spanContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }; - createTransactionAndMaybeSpan(type, transactionContext); - const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); - const baggage = propagation.createBaggage({ foo: { value: 'bar' } }); - propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter); - expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe( - 'foo=bar,sentry-environment=production,sentry-release=1.0.0,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b,sentry-transaction=sampled-transaction,sentry-sampled=true', - ); - }); - - it('should create baggage without active transaction', () => { - const spanContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }; - const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); - const baggage = propagation.createBaggage({ foo: { value: 'bar' } }); - propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter); - expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe('foo=bar'); - }); - - it('should NOT set baggage and sentry-trace header if instrumentation is supressed', () => { - const spanContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }; - const transactionContext = { - name: 'sampled-transaction', - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - sampled: true, - }; - createTransactionAndMaybeSpan(type, transactionContext); - const context = suppressTracing(trace.setSpanContext(ROOT_CONTEXT, spanContext)); - propagator.inject(context, carrier, defaultTextMapSetter); - expect(carrier[SENTRY_TRACE_HEADER]).toBe(undefined); - expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(undefined); - }); - }); - }); - }); - - describe('extract', () => { - it('sets sentry span context on the context', () => { - const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'; - carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(trace.getSpanContext(context)).toEqual({ - isRemote: true, - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - }); - }); - - it('sets defined sentry trace header on context', () => { - const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'; - carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(context.getValue(SENTRY_TRACE_PARENT_CONTEXT_KEY)).toEqual({ - parentSampled: true, - parentSpanId: '6e0c63257de34c92', - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - }); - }); - - it('sets undefined sentry trace header on context', () => { - const sentryTraceHeader = undefined; - carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(context.getValue(SENTRY_TRACE_PARENT_CONTEXT_KEY)).toEqual(undefined); - }); - - it('sets defined dynamic sampling context on context', () => { - const baggage = - 'sentry-environment=production,sentry-release=1.0.0,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b,sentry-transaction=dsc-transaction'; - carrier[SENTRY_BAGGAGE_HEADER] = baggage; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(context.getValue(SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY)).toEqual({ - environment: 'production', - public_key: 'abc', - release: '1.0.0', - trace_id: 'd4cda95b652f4a1592b449d5929fda1b', - transaction: 'dsc-transaction', - }); - }); - - it('sets undefined dynamic sampling context on context', () => { - const baggage = ''; - carrier[SENTRY_BAGGAGE_HEADER] = baggage; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(context.getValue(SENTRY_DYNAMIC_SAMPLING_CONTEXT_KEY)).toEqual(undefined); - }); - - it('handles when sentry-trace is an empty array', () => { - carrier[SENTRY_TRACE_HEADER] = []; - const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(context.getValue(SENTRY_TRACE_PARENT_CONTEXT_KEY)).toEqual(undefined); - }); - }); -}); diff --git a/packages/opentelemetry-node/test/spanprocessor.test.ts b/packages/opentelemetry-node/test/spanprocessor.test.ts deleted file mode 100644 index d874a26fa1f0..000000000000 --- a/packages/opentelemetry-node/test/spanprocessor.test.ts +++ /dev/null @@ -1,1009 +0,0 @@ -import type * as OpenTelemetry from '@opentelemetry/api'; -import { SpanKind } from '@opentelemetry/api'; -import { Resource } from '@opentelemetry/resources'; -import type { Span as OtelSpan } from '@opentelemetry/sdk-trace-base'; -import { NodeTracerProvider } from '@opentelemetry/sdk-trace-node'; -import { SemanticAttributes, SemanticResourceAttributes } from '@opentelemetry/semantic-conventions'; -import type { SpanStatusType } from '@sentry/core'; -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '@sentry/core'; -import { captureException, getCurrentScope, setCurrentClient } from '@sentry/core'; -import { SentrySpan, Transaction, addTracingExtensions, createTransport, spanToJSON } from '@sentry/core'; -import { NodeClient } from '@sentry/node-experimental'; -import { resolvedSyncPromise } from '@sentry/utils'; - -import { SentrySpanProcessor } from '../src/spanprocessor'; -import { SPAN_MAP, clearSpan, getSentrySpan } from '../src/utils/spanMap'; - -const SENTRY_DSN = 'https://0@0.ingest.sentry.io/0'; - -const DEFAULT_NODE_CLIENT_OPTIONS = { - dsn: SENTRY_DSN, - integrations: [], - transport: () => createTransport({ recordDroppedEvent: () => undefined }, _ => resolvedSyncPromise({})), - stackParser: () => [], -}; - -// Integration Test of SentrySpanProcessor - -beforeAll(() => { - addTracingExtensions(); -}); - -describe('SentrySpanProcessor', () => { - let client: NodeClient; - let provider: NodeTracerProvider; - let spanProcessor: SentrySpanProcessor; - - beforeEach(() => { - // To avoid test leakage, clear before each test - SPAN_MAP.clear(); - - client = new NodeClient(DEFAULT_NODE_CLIENT_OPTIONS); - setCurrentClient(client); - client.init(); - - spanProcessor = new SentrySpanProcessor(); - provider = new NodeTracerProvider({ - resource: new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: 'test-service', - }), - }); - provider.addSpanProcessor(spanProcessor); - provider.register(); - }); - - afterEach(async () => { - // Ensure test map is empty! - // Otherwise, we seem to have a leak somewhere... - expect(SPAN_MAP.size).toBe(0); - - await provider.forceFlush(); - await provider.shutdown(); - }); - - function getSpanForOtelSpan(otelSpan: OtelSpan | OpenTelemetry.Span) { - return getSentrySpan(otelSpan.spanContext().spanId); - } - - function getContext(transaction: Transaction) { - const transactionWithContext = transaction as unknown as Transaction; - // @ts-expect-error accessing private property - return transactionWithContext._contexts; - } - - it('creates a transaction', async () => { - const startTimestampMs = 1667381672309; - const endTimestampMs = 1667381672875; - const startTime = otelNumberToHrtime(startTimestampMs); - const endTime = otelNumberToHrtime(endTimestampMs); - - const otelSpan = provider.getTracer('default').startSpan('GET /users', { startTime }) as OtelSpan; - - const sentrySpanTransaction = getSpanForOtelSpan(otelSpan) as Transaction | undefined; - expect(sentrySpanTransaction).toBeInstanceOf(Transaction); - - expect(spanToJSON(sentrySpanTransaction!).description).toBe('GET /users'); - expect(spanToJSON(sentrySpanTransaction!).start_timestamp).toEqual(startTimestampMs / 1000); - expect(sentrySpanTransaction?.spanContext().traceId).toEqual(otelSpan.spanContext().traceId); - expect(spanToJSON(sentrySpanTransaction!).parent_span_id).toEqual(otelSpan.parentSpanId); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpanTransaction?.parentSpanId).toEqual(otelSpan.parentSpanId); - expect(sentrySpanTransaction?.spanContext().spanId).toEqual(otelSpan.spanContext().spanId); - - otelSpan.end(endTime); - - expect(spanToJSON(sentrySpanTransaction!).timestamp).toBe(endTimestampMs / 1000); - }); - - it('creates a child span if there is a running transaction', () => { - const startTimestampMs = 1667381672309; - const endTimestampMs = 1667381672875; - const startTime = otelNumberToHrtime(startTimestampMs); - const endTime = otelNumberToHrtime(endTimestampMs); - - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('SELECT * FROM users;', { startTime }, child => { - const childOtelSpan = child as OtelSpan; - - const sentrySpanTransaction = getSpanForOtelSpan(parentOtelSpan) as Transaction | undefined; - expect(sentrySpanTransaction).toBeInstanceOf(Transaction); - - const sentrySpan = getSpanForOtelSpan(childOtelSpan); - expect(sentrySpan).toBeInstanceOf(SentrySpan); - expect(sentrySpan ? spanToJSON(sentrySpan).description : undefined).toBe('SELECT * FROM users;'); - expect(spanToJSON(sentrySpan!).start_timestamp).toEqual(startTimestampMs / 1000); - expect(sentrySpan?.spanContext().spanId).toEqual(childOtelSpan.spanContext().spanId); - - expect(spanToJSON(sentrySpan!).parent_span_id).toEqual(sentrySpanTransaction?.spanContext().spanId); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.parentSpanId).toEqual(sentrySpanTransaction?.spanContext().spanId); - - // eslint-disable-next-line deprecation/deprecation - expect(getCurrentScope().getSpan()).toBeUndefined(); - - child.end(endTime); - - expect(spanToJSON(sentrySpan!).timestamp).toEqual(endTimestampMs / 1000); - }); - - parentOtelSpan.end(); - }); - }); - - it('handles a missing parent reference', () => { - const startTimestampMs = 1667381672309; - const endTimestampMs = 1667381672875; - const startTime = otelNumberToHrtime(startTimestampMs); - const endTime = otelNumberToHrtime(endTimestampMs); - - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - // We simulate the parent somehow not existing in our internal map - // this can happen if a race condition leads to spans being processed out of order - clearSpan(parentOtelSpan.spanContext().spanId); - - tracer.startActiveSpan('SELECT * FROM users;', { startTime }, child => { - const childOtelSpan = child as OtelSpan; - - // Parent span does not exist... - const sentrySpanTransaction = getSpanForOtelSpan(parentOtelSpan); - expect(sentrySpanTransaction).toBeUndefined(); - - // Span itself exists and is created as transaction - const sentrySpan = getSpanForOtelSpan(childOtelSpan); - expect(sentrySpan).toBeInstanceOf(SentrySpan); - expect(sentrySpan).toBeInstanceOf(Transaction); - expect(spanToJSON(sentrySpan!).description).toBe('SELECT * FROM users;'); - expect(spanToJSON(sentrySpan!).start_timestamp).toEqual(startTimestampMs / 1000); - expect(sentrySpan?.spanContext().spanId).toEqual(childOtelSpan.spanContext().spanId); - - expect(spanToJSON(sentrySpan!).parent_span_id).toEqual(parentOtelSpan.spanContext().spanId); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.parentSpanId).toEqual(parentOtelSpan.spanContext().spanId); - - // eslint-disable-next-line deprecation/deprecation - expect(getCurrentScope().getSpan()).toBeUndefined(); - - child.end(endTime); - - expect(spanToJSON(sentrySpan!).timestamp).toEqual(endTimestampMs / 1000); - }); - - parentOtelSpan.end(); - }); - }); - - it('allows to create multiple child spans on same level', () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - const sentrySpanTransaction = getSpanForOtelSpan(parentOtelSpan) as Transaction | undefined; - - expect(sentrySpanTransaction).toBeInstanceOf(SentrySpan); - expect(spanToJSON(sentrySpanTransaction!).description).toBe('GET /users'); - - // Create some parallel, independent spans - const span1 = tracer.startSpan('SELECT * FROM users;') as OtelSpan; - const span2 = tracer.startSpan('SELECT * FROM companies;') as OtelSpan; - const span3 = tracer.startSpan('SELECT * FROM locations;') as OtelSpan; - - const sentrySpan1 = getSpanForOtelSpan(span1); - const sentrySpan2 = getSpanForOtelSpan(span2); - const sentrySpan3 = getSpanForOtelSpan(span3); - - expect(spanToJSON(sentrySpan1!).parent_span_id).toEqual(sentrySpanTransaction?.spanContext().spanId); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan1?.parentSpanId).toEqual(sentrySpanTransaction?.spanContext().spanId); - - expect(spanToJSON(sentrySpan2!).parent_span_id).toEqual(sentrySpanTransaction?.spanContext().spanId); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan2?.parentSpanId).toEqual(sentrySpanTransaction?.spanContext().spanId); - - expect(spanToJSON(sentrySpan3!).parent_span_id).toEqual(sentrySpanTransaction?.spanContext().spanId); - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan3?.parentSpanId).toEqual(sentrySpanTransaction?.spanContext().spanId); - - expect(spanToJSON(sentrySpan1!).description).toEqual('SELECT * FROM users;'); - expect(spanToJSON(sentrySpan2!).description).toEqual('SELECT * FROM companies;'); - expect(spanToJSON(sentrySpan3!).description).toEqual('SELECT * FROM locations;'); - - span1.end(); - span2.end(); - span3.end(); - - parentOtelSpan.end(); - }); - }); - - it('handles child spans finished out of order', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parent => { - tracer.startActiveSpan('SELECT * FROM users;', child => { - const grandchild = tracer.startSpan('child 1'); - - const parentSpan = getSpanForOtelSpan(parent); - const childSpan = getSpanForOtelSpan(child); - const grandchildSpan = getSpanForOtelSpan(grandchild); - - parent.end(); - child.end(); - grandchild.end(); - - expect(parentSpan).toBeDefined(); - expect(childSpan).toBeDefined(); - expect(grandchildSpan).toBeDefined(); - - expect(spanToJSON(parentSpan!).timestamp).toBeDefined(); - expect(spanToJSON(childSpan!).timestamp).toBeDefined(); - expect(spanToJSON(grandchildSpan!).timestamp).toBeDefined(); - }); - }); - }); - - it('handles finished parent span before child span starts', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parent => { - const parentSpan = getSpanForOtelSpan(parent); - - parent.end(); - - tracer.startActiveSpan('SELECT * FROM users;', child => { - const childSpan = getSpanForOtelSpan(child); - - child.end(); - - expect(parentSpan).toBeDefined(); - expect(childSpan).toBeDefined(); - expect(parentSpan).toBeInstanceOf(Transaction); - expect(childSpan).toBeInstanceOf(Transaction); - expect(spanToJSON(parentSpan!).timestamp).toBeDefined(); - expect(spanToJSON(childSpan!).timestamp).toBeDefined(); - expect(spanToJSON(parentSpan!).parent_span_id).toBeUndefined(); - - expect(spanToJSON(parentSpan!).parent_span_id).toBeUndefined(); - // eslint-disable-next-line deprecation/deprecation - expect(parentSpan?.parentSpanId).toBeUndefined(); - - expect(spanToJSON(childSpan!).parent_span_id).toEqual(parentSpan?.spanContext().spanId); - // eslint-disable-next-line deprecation/deprecation - expect(childSpan?.parentSpanId).toEqual(parentSpan?.spanContext().spanId); - }); - }); - }); - - it('sets context for transaction', async () => { - const otelSpan = provider.getTracer('default').startSpan('GET /users'); - - const transaction = getSpanForOtelSpan(otelSpan) as Transaction; - - // context is only set after end - expect(getContext(transaction)).toEqual({}); - - otelSpan.end(); - - expect(getContext(transaction)).toEqual({ - otel: { - attributes: {}, - resource: { - 'service.name': 'test-service', - 'telemetry.sdk.language': 'nodejs', - 'telemetry.sdk.name': 'opentelemetry', - 'telemetry.sdk.version': '1.21.0', - }, - }, - }); - - // Start new transaction - const otelSpan2 = provider.getTracer('default').startSpan('GET /companies'); - - const transaction2 = getSpanForOtelSpan(otelSpan2) as Transaction; - - expect(getContext(transaction2)).toEqual({}); - - otelSpan2.setAttribute('test-attribute', 'test-value'); - - otelSpan2.end(); - - expect(getContext(transaction2)).toEqual({ - otel: { - attributes: { - 'test-attribute': 'test-value', - }, - resource: { - 'service.name': 'test-service', - 'telemetry.sdk.language': 'nodejs', - 'telemetry.sdk.name': 'opentelemetry', - 'telemetry.sdk.version': '1.21.0', - }, - }, - }); - }); - - it('sets data for span', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('SELECT * FROM users;', child => { - child.setAttribute('test-attribute', 'test-value'); - child.setAttribute('test-attribute-2', [1, 2, 3]); - child.setAttribute('test-attribute-3', 0); - child.setAttribute('test-attribute-4', false); - - const sentrySpan = getSpanForOtelSpan(child); - - // origin is set by default to 'manual' - expect(spanToJSON(sentrySpan!).data).toEqual({ 'sentry.origin': 'manual' }); - - child.end(); - - expect(spanToJSON(sentrySpan!).data).toEqual({ - 'otel.kind': 'INTERNAL', - 'test-attribute': 'test-value', - 'test-attribute-2': [1, 2, 3], - 'test-attribute-3': 0, - 'test-attribute-4': false, - 'sentry.origin': 'manual', - }); - }); - - parentOtelSpan.end(); - }); - }); - - it('sets status for transaction', async () => { - const otelSpan = provider.getTracer('default').startSpan('GET /users'); - - const transaction = getSpanForOtelSpan(otelSpan) as Transaction; - - // status is only set after end - expect(spanToJSON(transaction!).status).toBe(undefined); - - otelSpan.end(); - - expect(spanToJSON(transaction!).status).toBe('ok'); - }); - - it('sets status for span', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('SELECT * FROM users;', child => { - const sentrySpan = getSpanForOtelSpan(child); - - expect(spanToJSON(sentrySpan!).status).toBe(undefined); - - child.end(); - - expect(spanToJSON(sentrySpan!).status).toBe('ok'); - - parentOtelSpan.end(); - }); - }); - }); - - const statusTestTable: [number, undefined | number | string, undefined | string, SpanStatusType][] = [ - [-1, undefined, undefined, 'unknown_error'], - [3, undefined, undefined, 'unknown_error'], - [0, undefined, undefined, 'ok'], - [1, undefined, undefined, 'ok'], - [2, undefined, undefined, 'unknown_error'], - - // http codes - [2, 400, undefined, 'failed_precondition'], - [2, 401, undefined, 'unauthenticated'], - [2, 403, undefined, 'permission_denied'], - [2, 404, undefined, 'not_found'], - [2, 409, undefined, 'aborted'], - [2, 429, undefined, 'resource_exhausted'], - [2, 499, undefined, 'cancelled'], - [2, 500, undefined, 'internal_error'], - [2, 501, undefined, 'unimplemented'], - [2, 503, undefined, 'unavailable'], - [2, 504, undefined, 'deadline_exceeded'], - [2, 999, undefined, 'unknown_error'], - - [2, '400', undefined, 'failed_precondition'], - [2, '401', undefined, 'unauthenticated'], - [2, '403', undefined, 'permission_denied'], - [2, '404', undefined, 'not_found'], - [2, '409', undefined, 'aborted'], - [2, '429', undefined, 'resource_exhausted'], - [2, '499', undefined, 'cancelled'], - [2, '500', undefined, 'internal_error'], - [2, '501', undefined, 'unimplemented'], - [2, '503', undefined, 'unavailable'], - [2, '504', undefined, 'deadline_exceeded'], - [2, '999', undefined, 'unknown_error'], - - // grpc codes - [2, undefined, '1', 'cancelled'], - [2, undefined, '2', 'unknown_error'], - [2, undefined, '3', 'invalid_argument'], - [2, undefined, '4', 'deadline_exceeded'], - [2, undefined, '5', 'not_found'], - [2, undefined, '6', 'already_exists'], - [2, undefined, '7', 'permission_denied'], - [2, undefined, '8', 'resource_exhausted'], - [2, undefined, '9', 'failed_precondition'], - [2, undefined, '10', 'aborted'], - [2, undefined, '11', 'out_of_range'], - [2, undefined, '12', 'unimplemented'], - [2, undefined, '13', 'internal_error'], - [2, undefined, '14', 'unavailable'], - [2, undefined, '15', 'data_loss'], - [2, undefined, '16', 'unauthenticated'], - [2, undefined, '999', 'unknown_error'], - - // http takes precedence over grpc - [2, '400', '2', 'failed_precondition'], - ]; - - describe('convert otel span status', () => { - it.each(statusTestTable)( - 'works with otelStatus=%i, httpCode=%s, grpcCode=%s', - (otelStatus, httpCode, grpcCode, expected) => { - const otelSpan = provider.getTracer('default').startSpan('GET /users'); - const transaction = getSpanForOtelSpan(otelSpan) as Transaction; - - otelSpan.setStatus({ code: otelStatus }); - - if (httpCode) { - otelSpan.setAttribute(SemanticAttributes.HTTP_STATUS_CODE, httpCode); - } - - if (grpcCode) { - otelSpan.setAttribute(SemanticAttributes.RPC_GRPC_STATUS_CODE, grpcCode); - } - - otelSpan.end(); - expect(spanToJSON(transaction!).status).toBe(expected); - }, - ); - }); - - describe('update op/description', () => { - it('updates on end', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('SELECT * FROM users;', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.updateName('new name'); - - expect(sentrySpan && spanToJSON(sentrySpan).op).toBe(undefined); - expect(sentrySpan ? spanToJSON(sentrySpan).description : undefined).toBe('SELECT * FROM users;'); - - child.end(); - - expect(sentrySpan && spanToJSON(sentrySpan).op).toBe(undefined); - expect(sentrySpan ? spanToJSON(sentrySpan).description : undefined).toBe('new name'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for HTTP_METHOD for client', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('/users/all', { kind: SpanKind.CLIENT }, child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - - child.end(); - - expect(spanToJSON(sentrySpan!).op).toBe('http.client'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for HTTP_METHOD for server', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('/users/all', { kind: SpanKind.SERVER }, child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - - child.end(); - - expect(spanToJSON(sentrySpan!).op).toBe('http.server'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates op/description based on attributes for HTTP_METHOD without HTTP_ROUTE', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('HTTP GET', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - - child.end(); - - expect(sentrySpan ? spanToJSON(sentrySpan).description : undefined).toBe('HTTP GET'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for HTTP_METHOD with HTTP_ROUTE', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('HTTP GET', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - child.setAttribute(SemanticAttributes.HTTP_ROUTE, '/my/route/{id}'); - child.setAttribute(SemanticAttributes.HTTP_TARGET, '/my/route/123'); - child.setAttribute(SemanticAttributes.HTTP_URL, 'http://example.com/my/route/123'); - - child.end(); - - const { description, data } = spanToJSON(sentrySpan!); - - expect(description).toBe('GET /my/route/{id}'); - expect(data).toEqual({ - 'http.method': 'GET', - 'http.route': '/my/route/{id}', - 'http.target': '/my/route/123', - 'http.url': 'http://example.com/my/route/123', - 'otel.kind': 'INTERNAL', - url: 'http://example.com/my/route/123', - 'sentry.op': 'http', - 'sentry.origin': 'manual', - }); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for HTTP_METHOD with HTTP_TARGET', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('HTTP GET', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - child.setAttribute(SemanticAttributes.HTTP_TARGET, '/my/route/123'); - child.setAttribute(SemanticAttributes.HTTP_URL, 'http://example.com/my/route/123'); - - child.end(); - - const { description, data, op } = spanToJSON(sentrySpan!); - - expect(description).toBe('GET http://example.com/my/route/123'); - expect(data).toEqual({ - 'http.method': 'GET', - 'http.target': '/my/route/123', - 'http.url': 'http://example.com/my/route/123', - 'otel.kind': 'INTERNAL', - url: 'http://example.com/my/route/123', - 'sentry.op': 'http', - 'sentry.origin': 'manual', - }); - expect(op).toBe('http'); - - parentOtelSpan.end(); - }); - }); - }); - - it('Adds query & hash data based on HTTP_URL', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('HTTP GET', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - child.setAttribute(SemanticAttributes.HTTP_TARGET, '/my/route/123'); - child.setAttribute(SemanticAttributes.HTTP_URL, 'http://example.com/my/route/123?what=123#myHash'); - - child.end(); - - const { description, data, op } = spanToJSON(sentrySpan!); - - expect(description).toBe('GET http://example.com/my/route/123'); - expect(data).toEqual({ - 'http.method': 'GET', - 'http.target': '/my/route/123', - 'http.url': 'http://example.com/my/route/123?what=123#myHash', - 'otel.kind': 'INTERNAL', - url: 'http://example.com/my/route/123', - 'http.query': '?what=123', - 'http.fragment': '#myHash', - 'sentry.op': 'http', - 'sentry.origin': 'manual', - }); - expect(op).toBe('http'); - - parentOtelSpan.end(); - }); - }); - }); - - it('adds transaction source `url` for HTTP_TARGET', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', otelSpan => { - const sentrySpan = getSpanForOtelSpan(otelSpan); - - otelSpan.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - otelSpan.setAttribute(SemanticAttributes.HTTP_TARGET, '/my/route/123'); - - otelSpan.end(); - - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.transaction?.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('url'); - }); - }); - - it('adds transaction source `route` for root path HTTP_TARGET', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /', otelSpan => { - const sentrySpan = getSpanForOtelSpan(otelSpan); - - otelSpan.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - otelSpan.setAttribute(SemanticAttributes.HTTP_TARGET, '/'); - - otelSpan.end(); - - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.transaction?.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('route'); - }); - }); - - it('adds transaction source `url` for HTTP_ROUTE', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', otelSpan => { - const sentrySpan = getSpanForOtelSpan(otelSpan); - - otelSpan.setAttribute(SemanticAttributes.HTTP_METHOD, 'GET'); - otelSpan.setAttribute(SemanticAttributes.HTTP_ROUTE, '/my/route/:id'); - - otelSpan.end(); - - // eslint-disable-next-line deprecation/deprecation - expect(sentrySpan?.transaction?.attributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]).toBe('route'); - }); - }); - - it('updates based on attributes for DB_SYSTEM', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('fetch users from DB', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.DB_SYSTEM, 'MySQL'); - child.setAttribute(SemanticAttributes.DB_STATEMENT, 'SELECT * FROM users'); - - child.end(); - - const { description, op } = spanToJSON(sentrySpan!); - expect(op).toBe('db'); - expect(description).toBe('SELECT * FROM users'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for DB_SYSTEM without DB_STATEMENT', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('fetch users from DB', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.DB_SYSTEM, 'MySQL'); - - child.end(); - - const { description, op } = spanToJSON(sentrySpan!); - expect(op).toBe('db'); - expect(description).toBe('fetch users from DB'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for RPC_SERVICE', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('test operation', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.RPC_SERVICE, 'rpc service'); - - child.end(); - - const { op, description } = spanToJSON(sentrySpan!); - expect(op).toBe('rpc'); - expect(description).toBe('test operation'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for MESSAGING_SYSTEM', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('test operation', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.MESSAGING_SYSTEM, 'messaging system'); - - child.end(); - - const { op, description } = spanToJSON(sentrySpan!); - expect(op).toBe('message'); - expect(description).toBe('test operation'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates based on attributes for FAAS_TRIGGER', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('test operation', child => { - const sentrySpan = getSpanForOtelSpan(child); - - child.setAttribute(SemanticAttributes.FAAS_TRIGGER, 'test faas trigger'); - - child.end(); - - const { op, description } = spanToJSON(sentrySpan!); - expect(op).toBe('test faas trigger'); - expect(description).toBe('test operation'); - - parentOtelSpan.end(); - }); - }); - }); - - it('updates Sentry transaction', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('test operation', parentOtelSpan => { - const transaction = getSpanForOtelSpan(parentOtelSpan) as Transaction; - - parentOtelSpan.setAttribute(SemanticAttributes.FAAS_TRIGGER, 'test faas trigger'); - parentOtelSpan.end(); - - expect(spanToJSON(transaction).op).toBe('test faas trigger'); - expect(spanToJSON(transaction).description).toBe('test operation'); - }); - }); - }); - - describe('skip sentry requests', () => { - it('does not finish transaction for Sentry request', async () => { - const otelSpan = provider.getTracer('default').startSpan('POST to sentry', { - attributes: { - [SemanticAttributes.HTTP_METHOD]: 'POST', - [SemanticAttributes.HTTP_URL]: `${SENTRY_DSN}/sub/route`, - }, - }) as OtelSpan; - - const sentrySpanTransaction = getSpanForOtelSpan(otelSpan) as Transaction | undefined; - expect(sentrySpanTransaction).toBeDefined(); - - otelSpan.end(); - - expect(spanToJSON(sentrySpanTransaction!).timestamp).toBeUndefined(); - - // Ensure it is still removed from map! - expect(getSpanForOtelSpan(otelSpan)).toBeUndefined(); - }); - - it('finishes transaction for non-Sentry request', async () => { - const otelSpan = provider.getTracer('default').startSpan('POST to sentry', { - attributes: { - [SemanticAttributes.HTTP_METHOD]: 'POST', - [SemanticAttributes.HTTP_URL]: 'https://other.sentry.io/sub/route', - }, - }) as OtelSpan; - - const sentrySpanTransaction = getSpanForOtelSpan(otelSpan) as Transaction | undefined; - expect(sentrySpanTransaction).toBeDefined(); - - otelSpan.end(); - - expect(spanToJSON(sentrySpanTransaction!).timestamp).toBeDefined(); - }); - - it('does not finish spans for Sentry request', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parent => { - tracer.startActiveSpan( - 'SELECT * FROM users;', - { - attributes: { - [SemanticAttributes.HTTP_METHOD]: 'POST', - [SemanticAttributes.HTTP_URL]: `${SENTRY_DSN}/sub/route`, - }, - }, - child => { - const childOtelSpan = child as OtelSpan; - - const sentrySpan = getSpanForOtelSpan(childOtelSpan); - expect(sentrySpan).toBeDefined(); - - childOtelSpan.end(); - parent.end(); - - expect(spanToJSON(sentrySpan!).timestamp).toBeUndefined(); - - // Ensure it is still removed from map! - expect(getSpanForOtelSpan(childOtelSpan)).toBeUndefined(); - }, - ); - }); - }); - - it('handles child spans of Sentry requests normally', async () => { - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parent => { - tracer.startActiveSpan( - 'SELECT * FROM users;', - { - attributes: { - [SemanticAttributes.HTTP_METHOD]: 'POST', - [SemanticAttributes.HTTP_URL]: `${SENTRY_DSN}/sub/route`, - }, - }, - child => { - const grandchild = tracer.startSpan('child 1'); - - const sentrySpan = getSpanForOtelSpan(child); - expect(sentrySpan).toBeDefined(); - - const sentryGrandchildSpan = getSpanForOtelSpan(grandchild); - expect(sentryGrandchildSpan).toBeDefined(); - - grandchild.end(); - child.end(); - parent.end(); - - expect(spanToJSON(sentryGrandchildSpan!).timestamp).toBeDefined(); - expect(spanToJSON(sentrySpan!).timestamp).toBeUndefined(); - }, - ); - }); - }); - }); - - it('associates an error to a transaction', async () => { - let sentryEvent: any; - let otelSpan: any; - - // Clear provider & setup a new one - // As we need a custom client - await provider.forceFlush(); - await provider.shutdown(); - - client = new NodeClient({ - ...DEFAULT_NODE_CLIENT_OPTIONS, - beforeSend: event => { - sentryEvent = event; - return null; - }, - }); - setCurrentClient(client); - client.init(); - - // Need to register the spanprocessor again - spanProcessor = new SentrySpanProcessor(); - provider = new NodeTracerProvider({ - resource: new Resource({ - [SemanticResourceAttributes.SERVICE_NAME]: 'test-service', - }), - }); - provider.addSpanProcessor(spanProcessor); - provider.register(); - - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('SELECT * FROM users;', child => { - captureException(new Error('oh nooooo!')); - otelSpan = child as OtelSpan; - child.end(); - }); - - parentOtelSpan.end(); - }); - - expect(sentryEvent).toBeDefined(); - expect(sentryEvent.exception).toBeDefined(); - expect(sentryEvent.contexts.trace).toEqual({ - parent_span_id: otelSpan.parentSpanId, - span_id: otelSpan.spanContext().spanId, - trace_id: otelSpan.spanContext().traceId, - }); - }); - - it('generates Sentry errors from opentelemetry span exception events', () => { - let sentryEvent: any; - let otelSpan: any; - - client = new NodeClient({ - ...DEFAULT_NODE_CLIENT_OPTIONS, - beforeSend: event => { - sentryEvent = event; - return null; - }, - }); - setCurrentClient(client); - client.init(); - const tracer = provider.getTracer('default'); - - tracer.startActiveSpan('GET /users', parentOtelSpan => { - tracer.startActiveSpan('SELECT * FROM users;', child => { - child.recordException(new Error('this is an otel error!')); - otelSpan = child as OtelSpan; - child.end(); - }); - - parentOtelSpan.end(); - }); - - expect(sentryEvent).toBeDefined(); - expect(sentryEvent.exception).toBeDefined(); - expect(sentryEvent.exception.values[0]).toEqual({ - mechanism: expect.any(Object), - type: 'Error', - value: 'this is an otel error!', - }); - expect(sentryEvent.contexts.trace).toEqual({ - parent_span_id: otelSpan.parentSpanId, - span_id: otelSpan.spanContext().spanId, - trace_id: otelSpan.spanContext().traceId, - }); - }); -}); - -// OTEL expects a custom date format -const NANOSECOND_DIGITS = 9; -const SECOND_TO_NANOSECONDS = Math.pow(10, NANOSECOND_DIGITS); - -function otelNumberToHrtime(epochMillis: number): OpenTelemetry.HrTime { - const epochSeconds = epochMillis / 1000; - // Decimals only. - const seconds = Math.trunc(epochSeconds); - // Round sub-nanosecond accuracy to nanosecond. - const nanos = Number((epochSeconds - seconds).toFixed(NANOSECOND_DIGITS)) * SECOND_TO_NANOSECONDS; - return [seconds, nanos]; -} diff --git a/packages/opentelemetry-node/test/utils/captureExceptionForTimedEvent.test.ts b/packages/opentelemetry-node/test/utils/captureExceptionForTimedEvent.test.ts deleted file mode 100644 index 4d0c39b3a8b9..000000000000 --- a/packages/opentelemetry-node/test/utils/captureExceptionForTimedEvent.test.ts +++ /dev/null @@ -1,147 +0,0 @@ -import type { Span as OtelSpan, TimedEvent } from '@opentelemetry/sdk-trace-base'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import type { Hub } from '@sentry/types'; - -import { maybeCaptureExceptionForTimedEvent } from '../../src/utils/captureExceptionForTimedEvent'; - -describe('maybeCaptureExceptionForTimedEvent', () => { - it('ignores non-exception events', async () => { - const event: TimedEvent = { - time: [12345, 0], - name: 'test event', - }; - - const captureException = jest.fn(); - const hub = { - captureException, - } as unknown as Hub; - - maybeCaptureExceptionForTimedEvent(hub, event); - - expect(captureException).not.toHaveBeenCalled(); - }); - - it('ignores exception events without EXCEPTION_MESSAGE', async () => { - const event: TimedEvent = { - time: [12345, 0], - name: 'exception', - }; - - const captureException = jest.fn(); - const hub = { - captureException, - } as unknown as Hub; - - maybeCaptureExceptionForTimedEvent(hub, event); - - expect(captureException).not.toHaveBeenCalled(); - }); - - it('captures exception from event with EXCEPTION_MESSAGE', async () => { - const event: TimedEvent = { - time: [12345, 0], - name: 'exception', - attributes: { - [SemanticAttributes.EXCEPTION_MESSAGE]: 'test-message', - }, - }; - - const captureException = jest.fn(); - const hub = { - captureException, - } as unknown as Hub; - - maybeCaptureExceptionForTimedEvent(hub, event); - - expect(captureException).toHaveBeenCalledTimes(1); - expect(captureException).toHaveBeenCalledWith(expect.objectContaining({ message: 'test-message' }), { - captureContext: undefined, - }); - expect(captureException).toHaveBeenCalledWith(expect.any(Error), { - captureContext: undefined, - }); - }); - - it('captures stack and type, if available', async () => { - const event: TimedEvent = { - time: [12345, 0], - name: 'exception', - attributes: { - [SemanticAttributes.EXCEPTION_MESSAGE]: 'test-message', - [SemanticAttributes.EXCEPTION_STACKTRACE]: 'test-stack', - [SemanticAttributes.EXCEPTION_TYPE]: 'test-type', - }, - }; - - const captureException = jest.fn(); - const hub = { - captureException, - } as unknown as Hub; - - maybeCaptureExceptionForTimedEvent(hub, event); - - expect(captureException).toHaveBeenCalledTimes(1); - expect(captureException).toHaveBeenCalledWith( - expect.objectContaining({ message: 'test-message', name: 'test-type', stack: 'test-stack' }), - { - captureContext: undefined, - }, - ); - expect(captureException).toHaveBeenCalledWith(expect.any(Error), { - captureContext: undefined, - }); - }); - - it('captures span context, if available', async () => { - const event: TimedEvent = { - time: [12345, 0], - name: 'exception', - attributes: { - [SemanticAttributes.EXCEPTION_MESSAGE]: 'test-message', - }, - }; - - const span = { - parentSpanId: 'test-parent-span-id', - attributes: { - 'test-attr1': 'test-value1', - }, - resource: { - attributes: { - 'test-attr2': 'test-value2', - }, - }, - spanContext: () => { - return { spanId: 'test-span-id', traceId: 'test-trace-id' }; - }, - } as unknown as OtelSpan; - - const captureException = jest.fn(); - const hub = { - captureException, - } as unknown as Hub; - - maybeCaptureExceptionForTimedEvent(hub, event, span); - - expect(captureException).toHaveBeenCalledTimes(1); - expect(captureException).toHaveBeenCalledWith(expect.objectContaining({ message: 'test-message' }), { - captureContext: { - contexts: { - otel: { - attributes: { - 'test-attr1': 'test-value1', - }, - resource: { - 'test-attr2': 'test-value2', - }, - }, - trace: { - trace_id: 'test-trace-id', - span_id: 'test-span-id', - parent_span_id: 'test-parent-span-id', - }, - }, - }, - }); - }); -}); diff --git a/packages/opentelemetry-node/test/utils/parseOtelSpanDescription.test.ts b/packages/opentelemetry-node/test/utils/parseOtelSpanDescription.test.ts deleted file mode 100644 index b2d1b3654500..000000000000 --- a/packages/opentelemetry-node/test/utils/parseOtelSpanDescription.test.ts +++ /dev/null @@ -1,144 +0,0 @@ -import { SpanKind } from '@opentelemetry/api'; -import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; - -import { getSanitizedUrl } from '../../src/utils/parseOtelSpanDescription'; - -describe('getSanitizedUrl', () => { - it.each([ - [ - 'works without attributes', - {}, - SpanKind.CLIENT, - { - urlPath: undefined, - url: undefined, - fragment: undefined, - query: undefined, - }, - ], - [ - 'uses url without query for client request', - { - [SemanticAttributes.HTTP_URL]: 'http://example.com/?what=true', - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_TARGET]: '/?what=true', - [SemanticAttributes.HTTP_HOST]: 'example.com:80', - [SemanticAttributes.HTTP_STATUS_CODE]: 200, - }, - SpanKind.CLIENT, - { - urlPath: 'http://example.com/', - url: 'http://example.com/', - fragment: undefined, - query: '?what=true', - }, - ], - [ - 'uses url without hash for client request', - { - [SemanticAttributes.HTTP_URL]: 'http://example.com/sub#hash', - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_TARGET]: '/sub#hash', - [SemanticAttributes.HTTP_HOST]: 'example.com:80', - [SemanticAttributes.HTTP_STATUS_CODE]: 200, - }, - SpanKind.CLIENT, - { - urlPath: 'http://example.com/sub', - url: 'http://example.com/sub', - fragment: '#hash', - query: undefined, - }, - ], - [ - 'uses route if available for client request', - { - [SemanticAttributes.HTTP_URL]: 'http://example.com/?what=true', - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_TARGET]: '/?what=true', - [SemanticAttributes.HTTP_ROUTE]: '/my-route', - [SemanticAttributes.HTTP_HOST]: 'example.com:80', - [SemanticAttributes.HTTP_STATUS_CODE]: 200, - }, - SpanKind.CLIENT, - { - urlPath: '/my-route', - url: 'http://example.com/', - fragment: undefined, - query: '?what=true', - }, - ], - [ - 'falls back to target for client request if url not available', - { - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_TARGET]: '/?what=true', - [SemanticAttributes.HTTP_HOST]: 'example.com:80', - [SemanticAttributes.HTTP_STATUS_CODE]: 200, - }, - SpanKind.CLIENT, - { - urlPath: '/', - url: undefined, - fragment: undefined, - query: undefined, - }, - ], - [ - 'uses target without query for server request', - { - [SemanticAttributes.HTTP_URL]: 'http://example.com/?what=true', - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_TARGET]: '/?what=true', - [SemanticAttributes.HTTP_HOST]: 'example.com:80', - [SemanticAttributes.HTTP_STATUS_CODE]: 200, - }, - SpanKind.SERVER, - { - urlPath: '/', - url: 'http://example.com/', - fragment: undefined, - query: '?what=true', - }, - ], - [ - 'uses target without hash for server request', - { - [SemanticAttributes.HTTP_URL]: 'http://example.com/?what=true', - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_TARGET]: '/sub#hash', - [SemanticAttributes.HTTP_HOST]: 'example.com:80', - [SemanticAttributes.HTTP_STATUS_CODE]: 200, - }, - SpanKind.SERVER, - { - urlPath: '/sub', - url: 'http://example.com/', - fragment: undefined, - query: '?what=true', - }, - ], - [ - 'uses route for server request if available', - { - [SemanticAttributes.HTTP_URL]: 'http://example.com/?what=true', - [SemanticAttributes.HTTP_METHOD]: 'GET', - [SemanticAttributes.HTTP_TARGET]: '/?what=true', - [SemanticAttributes.HTTP_ROUTE]: '/my-route', - [SemanticAttributes.HTTP_HOST]: 'example.com:80', - [SemanticAttributes.HTTP_STATUS_CODE]: 200, - }, - SpanKind.SERVER, - { - urlPath: '/my-route', - url: 'http://example.com/', - fragment: undefined, - query: '?what=true', - }, - ], - ])('%s', (_, attributes, kind, expected) => { - const actual = getSanitizedUrl(attributes, kind); - - expect(actual).toEqual(expected); - }); -}); diff --git a/packages/opentelemetry-node/tsconfig.json b/packages/opentelemetry-node/tsconfig.json deleted file mode 100644 index bf45a09f2d71..000000000000 --- a/packages/opentelemetry-node/tsconfig.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "../../tsconfig.json", - - "include": ["src/**/*"], - - "compilerOptions": { - // package-specific options - } -} diff --git a/packages/opentelemetry-node/tsconfig.test.json b/packages/opentelemetry-node/tsconfig.test.json deleted file mode 100644 index 87f6afa06b86..000000000000 --- a/packages/opentelemetry-node/tsconfig.test.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "extends": "./tsconfig.json", - - "include": ["test/**/*"], - - "compilerOptions": { - // should include all types from `./tsconfig.json` plus types for all test frameworks used - "types": ["node", "jest"] - - // other package-specific, test-specific options - } -} diff --git a/packages/opentelemetry-node/tsconfig.types.json b/packages/opentelemetry-node/tsconfig.types.json deleted file mode 100644 index 65455f66bd75..000000000000 --- a/packages/opentelemetry-node/tsconfig.types.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "./tsconfig.json", - - "compilerOptions": { - "declaration": true, - "declarationMap": true, - "emitDeclarationOnly": true, - "outDir": "build/types" - } -} diff --git a/packages/opentelemetry/src/constants.ts b/packages/opentelemetry/src/constants.ts index 64b611af8c1c..0826ee6cb653 100644 --- a/packages/opentelemetry/src/constants.ts +++ b/packages/opentelemetry/src/constants.ts @@ -3,9 +3,7 @@ import { createContextKey } from '@opentelemetry/api'; export const SENTRY_TRACE_HEADER = 'sentry-trace'; export const SENTRY_BAGGAGE_HEADER = 'baggage'; export const SENTRY_TRACE_STATE_DSC = 'sentry.trace'; - -/** Context Key to hold a PropagationContext. */ -export const SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY = createContextKey('SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY'); +export const SENTRY_TRACE_STATE_PARENT_SPAN_ID = 'sentry.parent_span_id'; /** Context Key to hold a Hub. */ export const SENTRY_HUB_CONTEXT_KEY = createContextKey('sentry_hub'); diff --git a/packages/opentelemetry/src/contextManager.ts b/packages/opentelemetry/src/contextManager.ts index 73bbd145b978..9036667c2b40 100644 --- a/packages/opentelemetry/src/contextManager.ts +++ b/packages/opentelemetry/src/contextManager.ts @@ -9,6 +9,7 @@ import { } from './constants'; import { getCurrentHub } from './custom/getCurrentHub'; import { getScopesFromContext, setContextOnScope, setHubOnContext, setScopesOnContext } from './utils/contextData'; +import { setIsSetup } from './utils/setupCheck'; /** * Wrap an OpenTelemetry ContextManager in a way that ensures the context is kept in sync with the Sentry Hub. @@ -31,6 +32,10 @@ export function wrapContextManagerClass | undefined { - // If possible, we want to take the DSC from the active span - // That should take precedence over the DSC from the propagation context - const activeSpan = trace.getSpan(context); - const traceStateDsc = activeSpan?.spanContext().traceState?.get(SENTRY_TRACE_STATE_DSC); - const dscOnSpan = traceStateDsc ? baggageHeaderToDynamicSamplingContext(traceStateDsc) : undefined; - - if (dscOnSpan) { - return dscOnSpan; - } - - const propagationContext = getPropagationContextFromContext(context); - - if (propagationContext) { - const { traceId } = getSentryTraceData(context, propagationContext); - return getDynamicSamplingContext(propagationContext, traceId); - } - - return undefined; +import { + SENTRY_BAGGAGE_HEADER, + SENTRY_TRACE_HEADER, + SENTRY_TRACE_STATE_DSC, + SENTRY_TRACE_STATE_PARENT_SPAN_ID, +} from './constants'; +import { getScopesFromContext, setScopesOnContext } from './utils/contextData'; +import { setIsSetup } from './utils/setupCheck'; + +/** Get the Sentry propagation context from a span context. */ +export function getPropagationContextFromSpanContext(spanContext: SpanContext): PropagationContext { + const { traceId, spanId, traceFlags, traceState } = spanContext; + + const dscString = traceState ? traceState.get(SENTRY_TRACE_STATE_DSC) : undefined; + const dsc = dscString ? baggageHeaderToDynamicSamplingContext(dscString) : undefined; + const parentSpanId = traceState ? traceState.get(SENTRY_TRACE_STATE_PARENT_SPAN_ID) : undefined; + const sampled = traceFlags === TraceFlags.SAMPLED; + + return { + traceId, + spanId, + sampled, + parentSpanId, + dsc, + }; } /** * Injects and extracts `sentry-trace` and `baggage` headers from carriers. */ export class SentryPropagator extends W3CBaggagePropagator { + public constructor() { + super(); + setIsSetup('SentryPropagator'); + } + /** * @inheritDoc */ @@ -49,10 +57,7 @@ export class SentryPropagator extends W3CBaggagePropagator { let baggage = propagation.getBaggage(context) || propagation.createBaggage({}); - const propagationContext = getPropagationContextFromContext(context); - const { spanId, traceId, sampled } = getSentryTraceData(context, propagationContext); - - const dynamicSamplingContext = getDynamicSamplingContextFromContext(context); + const { dynamicSamplingContext, traceId, spanId, sampled } = getInjectionData(context); if (dynamicSamplingContext) { baggage = Object.entries(dynamicSamplingContext).reduce((b, [dscKey, dscValue]) => { @@ -83,15 +88,11 @@ export class SentryPropagator extends W3CBaggagePropagator { const propagationContext = propagationContextFromHeaders(sentryTraceHeader, maybeBaggageHeader); - // Add propagation context to context - const contextWithPropagationContext = setPropagationContextOnContext(context, propagationContext); - // We store the DSC as OTEL trace state on the span context - const dscString = propagationContext.dsc - ? dynamicSamplingContextToSentryBaggageHeader(propagationContext.dsc) - : undefined; - - const traceState = dscString ? new TraceState().set(SENTRY_TRACE_STATE_DSC, dscString) : undefined; + const traceState = makeTraceState({ + parentSpanId: propagationContext.parentSpanId, + dsc: propagationContext.dsc, + }); const spanContext: SpanContext = { traceId: propagationContext.traceId, @@ -101,8 +102,18 @@ export class SentryPropagator extends W3CBaggagePropagator { traceState, }; - // Add remote parent span context - return trace.setSpanContext(contextWithPropagationContext, spanContext); + // Add remote parent span context, + const ctxWithSpanContext = trace.setSpanContext(context, spanContext); + + // Also update the scope on the context (to be sure this is picked up everywhere) + const scopes = getScopesFromContext(ctxWithSpanContext); + const newScopes = { + scope: scopes ? scopes.scope.clone() : getCurrentScope().clone(), + isolationScope: scopes ? scopes.isolationScope : getIsolationScope(), + }; + newScopes.scope.setPropagationContext(propagationContext); + + return setScopesOnContext(ctxWithSpanContext, newScopes); } /** @@ -113,13 +124,91 @@ export class SentryPropagator extends W3CBaggagePropagator { } } -/** Get the DSC. */ +/** Exported for tests. */ +export function makeTraceState({ + parentSpanId, + dsc, +}: { parentSpanId?: string; dsc?: Partial }): TraceState | undefined { + if (!parentSpanId && !dsc) { + return undefined; + } + + // We store the DSC as OTEL trace state on the span context + const dscString = dsc ? dynamicSamplingContextToSentryBaggageHeader(dsc) : undefined; + + const traceStateBase = parentSpanId + ? new TraceState().set(SENTRY_TRACE_STATE_PARENT_SPAN_ID, parentSpanId) + : new TraceState(); + + return dscString ? traceStateBase.set(SENTRY_TRACE_STATE_DSC, dscString) : traceStateBase; +} + +function getInjectionData(context: Context): { + dynamicSamplingContext: Partial | undefined; + traceId: string | undefined; + spanId: string | undefined; + sampled: boolean | undefined; +} { + const span = trace.getSpan(context); + const spanIsRemote = span?.spanContext().isRemote; + + // If we have a local span, we can just pick everything from it + if (span && !spanIsRemote) { + const spanContext = span.spanContext(); + const propagationContext = getPropagationContextFromSpanContext(spanContext); + const dynamicSamplingContext = getDynamicSamplingContext(propagationContext, spanContext.traceId); + return { + dynamicSamplingContext, + traceId: spanContext.traceId, + spanId: spanContext.spanId, + sampled: spanContext.traceFlags === TraceFlags.SAMPLED, + }; + } + + // Else we try to use the propagation context from the scope + const scope = getScopesFromContext(context)?.scope; + if (scope) { + const propagationContext = scope.getPropagationContext(); + const dynamicSamplingContext = getDynamicSamplingContext(propagationContext, propagationContext.traceId); + return { + dynamicSamplingContext, + traceId: propagationContext.traceId, + spanId: propagationContext.spanId, + sampled: propagationContext.sampled, + }; + } + + // Else, we look at the remote span context + const spanContext = trace.getSpanContext(context); + if (spanContext) { + const propagationContext = getPropagationContextFromSpanContext(spanContext); + const dynamicSamplingContext = getDynamicSamplingContext(propagationContext, spanContext.traceId); + + return { + dynamicSamplingContext, + traceId: spanContext.traceId, + spanId: spanContext.spanId, + sampled: spanContext.traceFlags === TraceFlags.SAMPLED, + }; + } + + // If we have neither, there is nothing much we can do, but that should not happen usually + // Unless there is a detached OTEL context being passed around + return { + dynamicSamplingContext: undefined, + traceId: undefined, + spanId: undefined, + sampled: undefined, + }; +} + +/** Get the DSC from a context, or fall back to use the one from the client. */ function getDynamicSamplingContext( propagationContext: PropagationContext, traceId: string | undefined, ): Partial | undefined { // If we have a DSC on the propagation context, we just use it - if (propagationContext.dsc) { + if (propagationContext?.dsc) { return propagationContext.dsc; } @@ -132,30 +221,3 @@ function getDynamicSamplingContext( return undefined; } - -/** Get the trace data for propagation. */ -function getSentryTraceData( - context: Context, - propagationContext: PropagationContext | undefined, -): { - spanId: string | undefined; - traceId: string | undefined; - sampled: boolean | undefined; -} { - const span = trace.getSpan(context); - const spanContext = span && span.spanContext(); - - const traceId = spanContext ? spanContext.traceId : propagationContext?.traceId; - - // We have a few scenarios here: - // If we have an active span, and it is _not_ remote, we just use the span's ID - // If we have an active span that is remote, we do not want to use the spanId, as we don't want to attach it to the parent span - // If `isRemote === true`, the span is bascially virtual - // If we don't have a local active span, we use the generated spanId from the propagationContext - const spanId = spanContext && !spanContext.isRemote ? spanContext.spanId : propagationContext?.spanId; - - // eslint-disable-next-line no-bitwise - const sampled = spanContext ? Boolean(spanContext.traceFlags & TraceFlags.SAMPLED) : propagationContext?.sampled; - - return { traceId, spanId, sampled }; -} diff --git a/packages/opentelemetry/src/sampler.ts b/packages/opentelemetry/src/sampler.ts index 820c35ce371d..805824834add 100644 --- a/packages/opentelemetry/src/sampler.ts +++ b/packages/opentelemetry/src/sampler.ts @@ -8,8 +8,9 @@ import type { Client, ClientOptions, SamplingContext } from '@sentry/types'; import { isNaN, logger } from '@sentry/utils'; import { DEBUG_BUILD } from './debug-build'; +import { getPropagationContextFromSpanContext } from './propagator'; import { InternalSentrySemanticAttributes } from './semanticAttributes'; -import { getPropagationContextFromContext } from './utils/contextData'; +import { setIsSetup } from './utils/setupCheck'; /** * A custom OTEL sampler that uses Sentry sampling rates to make it's decision @@ -19,6 +20,7 @@ export class SentrySampler implements Sampler { public constructor(client: Client) { this._client = client; + setIsSetup('SentrySampler'); } /** @inheritDoc */ @@ -44,7 +46,7 @@ export class SentrySampler implements Sampler { // Note for testing: `isSpanContextValid()` checks the format of the traceId/spanId, so we need to pass valid ones if (parentContext && isSpanContextValid(parentContext) && parentContext.traceId === traceId) { if (parentContext.isRemote) { - parentSampled = getParentRemoteSampled(parentContext, context); + parentSampled = getParentRemoteSampled(parentContext); DEBUG_BUILD && logger.log(`[Tracing] Inheriting remote parent's sampled decision for ${spanName}: ${parentSampled}`); } else { @@ -178,10 +180,10 @@ function isValidSampleRate(rate: unknown): boolean { return true; } -function getParentRemoteSampled(spanContext: SpanContext, context: Context): boolean | undefined { +function getParentRemoteSampled(spanContext: SpanContext): boolean | undefined { const traceId = spanContext.traceId; - const traceparentData = getPropagationContextFromContext(context); + const traceparentData = getPropagationContextFromSpanContext(spanContext); - // Only inherit sample rate if `traceId` is the same + // Only inherit sampled if `traceId` is the same return traceparentData && traceId === traceparentData.traceId ? traceparentData.sampled : undefined; } diff --git a/packages/opentelemetry/src/setupEventContextTrace.ts b/packages/opentelemetry/src/setupEventContextTrace.ts index 8d45fb787326..93e9033f4969 100644 --- a/packages/opentelemetry/src/setupEventContextTrace.ts +++ b/packages/opentelemetry/src/setupEventContextTrace.ts @@ -28,8 +28,8 @@ export function setupEventContextTrace(client: Client): void { const rootSpan = getRootSpan(span); const transactionName = spanHasName(rootSpan) ? rootSpan.name : undefined; - if (transactionName) { - event.tags = { transaction: transactionName, ...event.tags }; + if (transactionName && !event.transaction) { + event.transaction = transactionName; } return event; diff --git a/packages/opentelemetry/src/spanExporter.ts b/packages/opentelemetry/src/spanExporter.ts index b673930be9a4..348304c82a92 100644 --- a/packages/opentelemetry/src/spanExporter.ts +++ b/packages/opentelemetry/src/spanExporter.ts @@ -200,14 +200,7 @@ function createTransactionForOtelSpan(span: ReadableSpan): Transaction { }); if (capturedSpanScopes) { - // Ensure the `transaction` tag is correctly set on the transaction event - const scope = capturedSpanScopes.scope.clone(); - scope.addEventProcessor(event => { - event.tags = { transaction: description, ...event.tags }; - return event; - }); - - setCapturedScopesOnTransaction(transaction, scope, capturedSpanScopes.isolationScope); + setCapturedScopesOnTransaction(transaction, capturedSpanScopes.scope, capturedSpanScopes.isolationScope); } return transaction; diff --git a/packages/opentelemetry/src/spanProcessor.ts b/packages/opentelemetry/src/spanProcessor.ts index fa40748c5c9c..2f348cc877ff 100644 --- a/packages/opentelemetry/src/spanProcessor.ts +++ b/packages/opentelemetry/src/spanProcessor.ts @@ -9,6 +9,7 @@ import { DEBUG_BUILD } from './debug-build'; import { SentrySpanExporter } from './spanExporter'; import { maybeCaptureExceptionForTimedEvent } from './utils/captureExceptionForTimedEvent'; import { getHubFromContext } from './utils/contextData'; +import { setIsSetup } from './utils/setupCheck'; import { getSpanHub, setSpanHub, setSpanParent, setSpanScopes } from './utils/spanData'; function onSpanStart(span: Span, parentContext: Context): void { @@ -56,6 +57,8 @@ function onSpanEnd(span: Span): void { export class SentrySpanProcessor extends BatchSpanProcessor implements SpanProcessorInterface { public constructor() { super(new SentrySpanExporter()); + + setIsSetup('SentrySpanProcessor'); } /** diff --git a/packages/opentelemetry/src/utils/contextData.ts b/packages/opentelemetry/src/utils/contextData.ts index 85c31ee822bf..5cb5cac79e3e 100644 --- a/packages/opentelemetry/src/utils/contextData.ts +++ b/packages/opentelemetry/src/utils/contextData.ts @@ -1,31 +1,11 @@ import type { Context } from '@opentelemetry/api'; -import type { Hub, PropagationContext, Scope } from '@sentry/types'; +import type { Hub, Scope } from '@sentry/types'; -import { - SENTRY_HUB_CONTEXT_KEY, - SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY, - SENTRY_SCOPES_CONTEXT_KEY, -} from '../constants'; +import { SENTRY_HUB_CONTEXT_KEY, SENTRY_SCOPES_CONTEXT_KEY } from '../constants'; import type { CurrentScopes } from '../types'; const SCOPE_CONTEXT_MAP = new WeakMap(); -/** - * Try to get the Propagation Context from the given OTEL context. - * This requires the SentryPropagator to be registered. - */ -export function getPropagationContextFromContext(context: Context): PropagationContext | undefined { - return context.getValue(SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY) as PropagationContext | undefined; -} - -/** - * Set a Propagation Context on an OTEL context.. - * This will return a forked context with the Propagation Context set. - */ -export function setPropagationContextOnContext(context: Context, propagationContext: PropagationContext): Context { - return context.setValue(SENTRY_PROPAGATION_CONTEXT_CONTEXT_KEY, propagationContext); -} - /** * Try to get the Hub from the given OTEL context. * This requires a Context Manager that was wrapped with getWrappedContextManager. diff --git a/packages/opentelemetry/src/utils/mapStatus.ts b/packages/opentelemetry/src/utils/mapStatus.ts index 065a626d1c38..ccde1c7ce178 100644 --- a/packages/opentelemetry/src/utils/mapStatus.ts +++ b/packages/opentelemetry/src/utils/mapStatus.ts @@ -1,12 +1,13 @@ import { SpanStatusCode } from '@opentelemetry/api'; import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import type { SpanStatusType as SentryStatus } from '@sentry/core'; +import { SPAN_STATUS_ERROR, SPAN_STATUS_OK } from '@sentry/core'; +import type { SpanStatus } from '@sentry/types'; import type { AbstractSpan } from '../types'; import { spanHasAttributes, spanHasStatus } from './spanTypes'; // canonicalCodesHTTPMap maps some HTTP codes to Sentry's span statuses. See possible mapping in https://develop.sentry.dev/sdk/event-payloads/span/ -const canonicalCodesHTTPMap: Record = { +const canonicalCodesHTTPMap: Record = { '400': 'failed_precondition', '401': 'unauthenticated', '403': 'permission_denied', @@ -21,7 +22,7 @@ const canonicalCodesHTTPMap: Record = { } as const; // canonicalCodesGrpcMap maps some GRPC codes to Sentry's span statuses. See description in grpc documentation. -const canonicalCodesGrpcMap: Record = { +const canonicalCodesGrpcMap: Record = { '1': 'cancelled', '2': 'unknown_error', '3': 'invalid_argument', @@ -43,7 +44,7 @@ const canonicalCodesGrpcMap: Record = { /** * Get a Sentry span status from an otel span. */ -export function mapStatus(span: AbstractSpan): SentryStatus { +export function mapStatus(span: AbstractSpan): SpanStatus { const attributes = spanHasAttributes(span) ? span.attributes : {}; const status = spanHasStatus(span) ? span.status : undefined; @@ -54,21 +55,21 @@ export function mapStatus(span: AbstractSpan): SentryStatus { if (code) { const sentryStatus = canonicalCodesHTTPMap[code]; if (sentryStatus) { - return sentryStatus; + return { code: SPAN_STATUS_ERROR, message: sentryStatus }; } } if (typeof grpcCode === 'string') { const sentryStatus = canonicalCodesGrpcMap[grpcCode]; if (sentryStatus) { - return sentryStatus; + return { code: SPAN_STATUS_ERROR, message: sentryStatus }; } } const statusCode = status && status.code; if (statusCode === SpanStatusCode.OK || statusCode === SpanStatusCode.UNSET) { - return 'ok'; + return { code: SPAN_STATUS_OK }; } - return 'unknown_error'; + return { code: SPAN_STATUS_ERROR, message: 'unknown_error' }; } diff --git a/packages/opentelemetry/src/utils/setupCheck.ts b/packages/opentelemetry/src/utils/setupCheck.ts new file mode 100644 index 000000000000..66bc7b445f83 --- /dev/null +++ b/packages/opentelemetry/src/utils/setupCheck.ts @@ -0,0 +1,18 @@ +type OpenTelemetryElement = 'SentrySpanProcessor' | 'SentryContextManager' | 'SentryPropagator' | 'SentrySampler'; + +const setupElements = new Set(); + +/** Get all the OpenTelemetry elements that have been set up. */ +export function openTelemetrySetupCheck(): OpenTelemetryElement[] { + return Array.from(setupElements); +} + +/** Mark an OpenTelemetry element as setup. */ +export function setIsSetup(element: OpenTelemetryElement): void { + setupElements.add(element); +} + +/** Only exported for tests. */ +export function clearOpenTelemetrySetupCheck(): void { + setupElements.clear(); +} diff --git a/packages/opentelemetry/test/helpers/mockSdkInit.ts b/packages/opentelemetry/test/helpers/mockSdkInit.ts index d4a41f90959e..6a3d9b171833 100644 --- a/packages/opentelemetry/test/helpers/mockSdkInit.ts +++ b/packages/opentelemetry/test/helpers/mockSdkInit.ts @@ -4,6 +4,7 @@ import type { ClientOptions, Options } from '@sentry/types'; import { getCurrentScope, getGlobalScope, getIsolationScope } from '@sentry/core'; import { setOpenTelemetryContextAsyncContextStrategy } from '../../src/asyncContextStrategy'; +import { clearOpenTelemetrySetupCheck } from '../../src/utils/setupCheck'; import { init as initTestClient } from './TestClient'; import { initOtel } from './initOtel'; @@ -32,6 +33,7 @@ export function mockSdkInit(options?: Partial) { } export function cleanupOtel(_provider?: BasicTracerProvider): void { + clearOpenTelemetrySetupCheck(); const provider = getProvider(_provider); if (!provider) { diff --git a/packages/opentelemetry/test/integration/scope.test.ts b/packages/opentelemetry/test/integration/scope.test.ts index 7a32acbe2364..36c794b1dbb0 100644 --- a/packages/opentelemetry/test/integration/scope.test.ts +++ b/packages/opentelemetry/test/integration/scope.test.ts @@ -90,8 +90,8 @@ describe('Integration | Scope', () => { tag2: 'val2', tag3: 'val3', tag4: 'val4', - ...(enableTracing ? { transaction: 'outer' } : {}), }, + ...(enableTracing ? { transaction: 'outer' } : undefined), }), { event_id: expect.any(String), @@ -126,7 +126,6 @@ describe('Integration | Scope', () => { tag2: 'val2', tag3: 'val3', tag4: 'val4', - transaction: 'outer', }, timestamp: expect.any(Number), transaction: 'outer', @@ -220,8 +219,8 @@ describe('Integration | Scope', () => { tag2: 'val2a', tag3: 'val3a', tag4: 'val4a', - ...(enableTracing ? { transaction: 'outer' } : {}), }, + ...(enableTracing ? { transaction: 'outer' } : undefined), }), { event_id: expect.any(String), @@ -246,8 +245,8 @@ describe('Integration | Scope', () => { tag2: 'val2b', tag3: 'val3b', tag4: 'val4b', - ...(enableTracing ? { transaction: 'outer' } : {}), }, + ...(enableTracing ? { transaction: 'outer' } : undefined), }), { event_id: expect.any(String), @@ -346,8 +345,8 @@ describe('Integration | Scope', () => { tag4: 'val4a', isolationTag1: 'val1', isolationTag2: 'val2', - ...(enableTracing ? { transaction: 'outer' } : {}), }, + ...(enableTracing ? { transaction: 'outer' } : undefined), }), { event_id: expect.any(String), @@ -374,8 +373,8 @@ describe('Integration | Scope', () => { tag4: 'val4b', isolationTag1: 'val1', isolationTag2: 'val2b', - ...(enableTracing ? { transaction: 'outer' } : {}), }, + ...(enableTracing ? { transaction: 'outer' } : undefined), }), { event_id: expect.any(String), diff --git a/packages/opentelemetry/test/integration/transactions.test.ts b/packages/opentelemetry/test/integration/transactions.test.ts index 4e915101d77e..99d46abad4f9 100644 --- a/packages/opentelemetry/test/integration/transactions.test.ts +++ b/packages/opentelemetry/test/integration/transactions.test.ts @@ -1,16 +1,15 @@ import type { SpanContext } from '@opentelemetry/api'; +import { ROOT_CONTEXT } from '@opentelemetry/api'; import { TraceFlags, context, trace } from '@opentelemetry/api'; import type { SpanProcessor } from '@opentelemetry/sdk-trace-base'; import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, addBreadcrumb, getClient, setTag, withIsolationScope } from '@sentry/core'; -import type { Event, PropagationContext, TransactionEvent } from '@sentry/types'; +import type { Event, TransactionEvent } from '@sentry/types'; import { logger } from '@sentry/utils'; import { TraceState } from '@opentelemetry/core'; -import { spanToJSON } from '@sentry/core'; import { SENTRY_TRACE_STATE_DSC } from '../../src/constants'; import { SentrySpanProcessor } from '../../src/spanProcessor'; import { startInactiveSpan, startSpan } from '../../src/trace'; -import { setPropagationContextOnContext } from '../../src/utils/contextData'; import type { TestClientInterface } from '../helpers/TestClient'; import { cleanupOtel, getProvider, mockSdkInit } from '../helpers/mockSdkInit'; @@ -128,7 +127,6 @@ describe('Integration | Transactions', () => { tags: { 'outer.tag': 'test value', 'test.tag': 'test value', - transaction: 'test name', }, timestamp: expect.any(Number), transaction: 'test name', @@ -146,7 +144,7 @@ describe('Integration | Transactions', () => { // note: Currently, spans do not have any context/span added to them // This is the same behavior as for the "regular" SDKs - expect(spans.map(span => spanToJSON(span))).toEqual([ + expect(spans).toEqual([ { data: { 'otel.kind': 'INTERNAL', @@ -271,7 +269,9 @@ describe('Integration | Transactions', () => { }), spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), - tags: { 'test.tag': 'test value', transaction: 'test name' }, + tags: { + 'test.tag': 'test value', + }, timestamp: expect.any(Number), transaction: 'test name', transaction_info: { source: 'task' }, @@ -314,7 +314,9 @@ describe('Integration | Transactions', () => { }), spans: [expect.any(Object), expect.any(Object)], start_timestamp: expect.any(Number), - tags: { 'test.tag': 'test value b', transaction: 'test name b' }, + tags: { + 'test.tag': 'test value b', + }, timestamp: expect.any(Number), transaction: 'test name b', transaction_info: { source: 'custom' }, @@ -340,29 +342,19 @@ describe('Integration | Transactions', () => { traceFlags: TraceFlags.SAMPLED, }; - const propagationContext: PropagationContext = { - traceId, - parentSpanId, - spanId: '6e0c63257de34c93', - sampled: true, - }; - mockSdkInit({ enableTracing: true, beforeSendTransaction }); const client = getClient() as TestClientInterface; // We simulate the correct context we'd normally get from the SentryPropagator - context.with( - trace.setSpanContext(setPropagationContextOnContext(context.active(), propagationContext), spanContext), - () => { - startSpan({ op: 'test op', name: 'test name', source: 'task', origin: 'auto.test' }, () => { - const subSpan = startInactiveSpan({ name: 'inner span 1' }); - subSpan.end(); - - startSpan({ name: 'inner span 2' }, () => {}); - }); - }, - ); + context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => { + startSpan({ op: 'test op', name: 'test name', source: 'task', origin: 'auto.test' }, () => { + const subSpan = startInactiveSpan({ name: 'inner span 1' }); + subSpan.end(); + + startSpan({ name: 'inner span 2' }, () => {}); + }); + }); await client.flush(); @@ -413,7 +405,7 @@ describe('Integration | Transactions', () => { // note: Currently, spans do not have any context/span added to them // This is the same behavior as for the "regular" SDKs - expect(spans.map(span => spanToJSON(span))).toEqual([ + expect(spans).toEqual([ { data: { 'otel.kind': 'INTERNAL', @@ -551,36 +543,26 @@ describe('Integration | Transactions', () => { traceState: new TraceState().set(SENTRY_TRACE_STATE_DSC, dscString), }; - const propagationContext: PropagationContext = { - traceId, - parentSpanId, - spanId: '6e0c63257de34c93', - sampled: true, - }; - mockSdkInit({ enableTracing: true, beforeSendTransaction }); const client = getClient() as TestClientInterface; // We simulate the correct context we'd normally get from the SentryPropagator - context.with( - trace.setSpanContext(setPropagationContextOnContext(context.active(), propagationContext), spanContext), - () => { - startSpan({ op: 'test op', name: 'test name', source: 'task', origin: 'auto.test' }, span => { - expect(span.spanContext().traceState?.get(SENTRY_TRACE_STATE_DSC)).toEqual(dscString); + context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => { + startSpan({ op: 'test op', name: 'test name', source: 'task', origin: 'auto.test' }, span => { + expect(span.spanContext().traceState?.get(SENTRY_TRACE_STATE_DSC)).toEqual(dscString); - const subSpan = startInactiveSpan({ name: 'inner span 1' }); + const subSpan = startInactiveSpan({ name: 'inner span 1' }); - expect(subSpan.spanContext().traceState?.get(SENTRY_TRACE_STATE_DSC)).toEqual(dscString); + expect(subSpan.spanContext().traceState?.get(SENTRY_TRACE_STATE_DSC)).toEqual(dscString); - subSpan.end(); + subSpan.end(); - startSpan({ name: 'inner span 2' }, subSpan => { - expect(subSpan.spanContext().traceState?.get(SENTRY_TRACE_STATE_DSC)).toEqual(dscString); - }); + startSpan({ name: 'inner span 2' }, subSpan => { + expect(subSpan.spanContext().traceState?.get(SENTRY_TRACE_STATE_DSC)).toEqual(dscString); }); - }, - ); + }); + }); await client.flush(); diff --git a/packages/opentelemetry/test/propagator.test.ts b/packages/opentelemetry/test/propagator.test.ts index 27436117d885..50629e965713 100644 --- a/packages/opentelemetry/test/propagator.test.ts +++ b/packages/opentelemetry/test/propagator.test.ts @@ -1,18 +1,19 @@ import { ROOT_CONTEXT, TraceFlags, + context, defaultTextMapGetter, defaultTextMapSetter, propagation, trace, } from '@opentelemetry/api'; -import { TraceState, suppressTracing } from '@opentelemetry/core'; -import { addTracingExtensions, setCurrentClient } from '@sentry/core'; -import type { Client, PropagationContext } from '@sentry/types'; +import { suppressTracing } from '@opentelemetry/core'; +import { addTracingExtensions, withScope } from '@sentry/core'; -import { SENTRY_BAGGAGE_HEADER, SENTRY_TRACE_HEADER, SENTRY_TRACE_STATE_DSC } from '../src/constants'; -import { SentryPropagator } from '../src/propagator'; -import { getPropagationContextFromContext, setPropagationContextOnContext } from '../src/utils/contextData'; +import { SENTRY_BAGGAGE_HEADER, SENTRY_SCOPES_CONTEXT_KEY, SENTRY_TRACE_HEADER } from '../src/constants'; +import { SentryPropagator, makeTraceState } from '../src/propagator'; +import { getScopesFromContext } from '../src/utils/contextData'; +import { cleanupOtel, mockSdkInit } from './helpers/mockSdkInit'; beforeAll(() => { addTracingExtensions(); @@ -24,6 +25,16 @@ describe('SentryPropagator', () => { beforeEach(() => { carrier = {}; + mockSdkInit({ + environment: 'production', + release: '1.0.0', + enableTracing: true, + dsn: 'https://abc@domain/123', + }); + }); + + afterEach(() => { + cleanupOtel(); }); it('returns fields set', () => { @@ -31,289 +42,454 @@ describe('SentryPropagator', () => { }); describe('inject', () => { - const client = { - getOptions: () => ({ - environment: 'production', - release: '1.0.0', - }), - getDsn: () => ({ - publicKey: 'abc', - }), - emit: () => {}, - } as unknown as Client; - - setCurrentClient(client); - - describe('with active span', () => { + describe('without active local span', () => { it.each([ [ - 'works with a sampled propagation context', + 'uses remote spanContext without DSC for sampled remote span', { traceId: 'd4cda95b652f4a1592b449d5929fda1b', spanId: '6e0c63257de34c92', traceFlags: TraceFlags.SAMPLED, - }, - { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c94', - parentSpanId: '6e0c63257de34c93', - sampled: true, - dsc: { - transaction: 'sampled-transaction', - trace_id: 'd4cda95b652f4a1592b449d5929fda1b', - sampled: 'true', - public_key: 'abc', - environment: 'production', - release: '1.0.0', - }, + isRemote: true, }, [ 'sentry-environment=production', 'sentry-release=1.0.0', 'sentry-public_key=abc', 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - 'sentry-transaction=sampled-transaction', - 'sentry-sampled=true', ], 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1', ], [ - 'works with a DSC on the span trace state', + 'uses remote spanContext without DSC for unsampled remote span', { traceId: 'd4cda95b652f4a1592b449d5929fda1b', spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - traceState: new TraceState().set( - SENTRY_TRACE_STATE_DSC, - 'sentry-transaction=other-transaction,sentry-environment=other,sentry-release=8.0.0,sentry-public_key=public,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b,sentry-sampled=true', - ), + traceFlags: TraceFlags.NONE, + isRemote: true, }, + [ + 'sentry-environment=production', + 'sentry-release=1.0.0', + 'sentry-public_key=abc', + 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', + ], + 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-0', + ], + [ + 'uses remote spanContext with DSC for sampled remote span', { traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c94', - parentSpanId: '6e0c63257de34c93', - sampled: true, + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + traceState: makeTraceState({ + parentSpanId: '6e0c63257de34c92', + dsc: { + transaction: 'sampled-transaction', + sampled: 'true', + trace_id: 'dsc_trace_id', + public_key: 'dsc_public_key', + environment: 'dsc_environment', + release: 'dsc_release', + sample_rate: '0.5', + replay_id: 'dsc_replay_id', + }, + }), + isRemote: true, }, [ - 'sentry-environment=other', - 'sentry-release=8.0.0', - 'sentry-public_key=public', - 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - 'sentry-transaction=other-transaction', + 'sentry-environment=dsc_environment', + 'sentry-release=dsc_release', + 'sentry-public_key=dsc_public_key', + 'sentry-trace_id=dsc_trace_id', + 'sentry-transaction=sampled-transaction', 'sentry-sampled=true', + 'sentry-sample_rate=0.5', + 'sentry-replay_id=dsc_replay_id', ], 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1', ], [ - 'works with an unsampled propagation context', + 'uses remote spanContext with DSC for unsampled remote span', { traceId: 'd4cda95b652f4a1592b449d5929fda1b', spanId: '6e0c63257de34c92', traceFlags: TraceFlags.NONE, + traceState: makeTraceState({ + parentSpanId: '6e0c63257de34c92', + dsc: { + transaction: 'sampled-transaction', + sampled: 'false', + trace_id: 'dsc_trace_id', + public_key: 'dsc_public_key', + environment: 'dsc_environment', + release: 'dsc_release', + sample_rate: '0.5', + replay_id: 'dsc_replay_id', + }, + }), + isRemote: true, }, - { + [ + 'sentry-environment=dsc_environment', + 'sentry-release=dsc_release', + 'sentry-public_key=dsc_public_key', + 'sentry-trace_id=dsc_trace_id', + 'sentry-transaction=sampled-transaction', + 'sentry-sampled=false', + 'sentry-sample_rate=0.5', + 'sentry-replay_id=dsc_replay_id', + ], + 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-0', + ], + ])('%s', (_name, spanContext, baggage, sentryTrace) => { + const ctx = trace.setSpanContext(ROOT_CONTEXT, spanContext); + propagator.inject(ctx, carrier, defaultTextMapSetter); + + expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(baggage.sort()); + expect(carrier[SENTRY_TRACE_HEADER]).toBe(sentryTrace); + }); + + it('uses scope propagation context without DSC if no span is found', () => { + withScope(scope => { + scope.setPropagationContext({ traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c94', parentSpanId: '6e0c63257de34c93', - sampled: false, + spanId: '6e0c63257de34c92', + sampled: true, + }); + + propagator.inject(context.active(), carrier, defaultTextMapSetter); + + expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual( + [ + 'sentry-environment=production', + 'sentry-release=1.0.0', + 'sentry-public_key=abc', + 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', + ].sort(), + ); + expect(carrier[SENTRY_TRACE_HEADER]).toBe('d4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'); + }); + }); + + it('uses scope propagation context with DSC if no span is found', () => { + withScope(scope => { + scope.setPropagationContext({ + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + parentSpanId: '6e0c63257de34c93', + spanId: '6e0c63257de34c92', + sampled: true, dsc: { - transaction: 'not-sampled-transaction', - trace_id: 'd4cda95b652f4a1592b449d5929fda1b', + transaction: 'sampled-transaction', sampled: 'false', - public_key: 'abc', - environment: 'production', - release: '1.0.0', + trace_id: 'dsc_trace_id', + public_key: 'dsc_public_key', + environment: 'dsc_environment', + release: 'dsc_release', + sample_rate: '0.5', + replay_id: 'dsc_replay_id', }, + }); + + propagator.inject(context.active(), carrier, defaultTextMapSetter); + + expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual( + [ + 'sentry-environment=dsc_environment', + 'sentry-release=dsc_release', + 'sentry-public_key=dsc_public_key', + 'sentry-trace_id=dsc_trace_id', + 'sentry-transaction=sampled-transaction', + 'sentry-sampled=false', + 'sentry-sample_rate=0.5', + 'sentry-replay_id=dsc_replay_id', + ].sort(), + ); + expect(carrier[SENTRY_TRACE_HEADER]).toBe('d4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'); + }); + }); + + it('uses scope propagation context over remote spanContext', () => { + context.with( + trace.setSpanContext(ROOT_CONTEXT, { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.NONE, + isRemote: true, + }), + () => { + withScope(scope => { + scope.setPropagationContext({ + traceId: 'TRACE_ID', + parentSpanId: 'PARENT_SPAN_ID', + spanId: 'SPAN_ID', + sampled: true, + }); + + propagator.inject(context.active(), carrier, defaultTextMapSetter); + + expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual( + [ + 'sentry-environment=production', + 'sentry-release=1.0.0', + 'sentry-public_key=abc', + 'sentry-trace_id=TRACE_ID', + ].sort(), + ); + expect(carrier[SENTRY_TRACE_HEADER]).toBe('TRACE_ID-SPAN_ID-1'); + }); + }, + ); + }); + + it('creates random traceId & spanId if no scope & span is found', () => { + const ctx = trace.deleteSpan(ROOT_CONTEXT).deleteValue(SENTRY_SCOPES_CONTEXT_KEY); + propagator.inject(ctx, carrier, defaultTextMapSetter); + + expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual([]); + expect(carrier[SENTRY_TRACE_HEADER]).toMatch(/^\w{32}-\w{16}$/); + }); + }); + + describe('with active span', () => { + it.each([ + [ + 'continues a remote trace without dsc', + { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + isRemote: true, }, [ 'sentry-environment=production', 'sentry-release=1.0.0', 'sentry-public_key=abc', 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - 'sentry-transaction=not-sampled-transaction', - 'sentry-sampled=false', ], - 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-0', + 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-1', ], [ - 'creates a new DSC if none exists yet', + 'continues a remote trace with dsc', { traceId: 'd4cda95b652f4a1592b449d5929fda1b', spanId: '6e0c63257de34c92', traceFlags: TraceFlags.SAMPLED, + isRemote: true, + traceState: makeTraceState({ + parentSpanId: '6e0c63257de34c92', + dsc: { + transaction: 'sampled-transaction', + sampled: 'true', + trace_id: 'dsc_trace_id', + public_key: 'dsc_public_key', + environment: 'dsc_environment', + release: 'dsc_release', + sample_rate: '0.5', + replay_id: 'dsc_replay_id', + }, + }), }, + [ + 'sentry-environment=dsc_environment', + 'sentry-release=dsc_release', + 'sentry-public_key=dsc_public_key', + 'sentry-trace_id=dsc_trace_id', + 'sentry-transaction=sampled-transaction', + 'sentry-sampled=true', + 'sentry-sample_rate=0.5', + 'sentry-replay_id=dsc_replay_id', + ], + 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-1', + ], + [ + 'continues an unsampled remote trace without dsc', { traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c94', - parentSpanId: '6e0c63257de34c93', - sampled: true, - dsc: undefined, + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.NONE, + isRemote: true, }, [ 'sentry-environment=production', - 'sentry-public_key=abc', 'sentry-release=1.0.0', + 'sentry-public_key=abc', 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', ], - 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1', + 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-0', ], [ - 'works with a remote parent span', + 'continues an unsampled remote trace with dsc', { traceId: 'd4cda95b652f4a1592b449d5929fda1b', spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, + traceFlags: TraceFlags.NONE, isRemote: true, + traceState: makeTraceState({ + parentSpanId: '6e0c63257de34c92', + dsc: { + transaction: 'sampled-transaction', + sampled: 'false', + trace_id: 'dsc_trace_id', + public_key: 'dsc_public_key', + environment: 'dsc_environment', + release: 'dsc_release', + sample_rate: '0.5', + replay_id: 'dsc_replay_id', + }, + }), }, + [ + 'sentry-environment=dsc_environment', + 'sentry-release=dsc_release', + 'sentry-public_key=dsc_public_key', + 'sentry-trace_id=dsc_trace_id', + 'sentry-transaction=sampled-transaction', + 'sentry-sampled=false', + 'sentry-sample_rate=0.5', + 'sentry-replay_id=dsc_replay_id', + ], + 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-0', + ], + [ + 'starts a new trace without existing dsc', { traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c94', - parentSpanId: '6e0c63257de34c93', - sampled: true, - dsc: { - transaction: 'sampled-transaction', - trace_id: 'd4cda95b652f4a1592b449d5929fda1b', - sampled: 'true', - public_key: 'abc', - environment: 'production', - release: '1.0.0', - }, + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, }, [ 'sentry-environment=production', 'sentry-release=1.0.0', 'sentry-public_key=abc', 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - 'sentry-transaction=sampled-transaction', - 'sentry-sampled=true', ], - 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c94-1', + 'd4cda95b652f4a1592b449d5929fda1b-{{spanId}}-1', ], - ])('%s', (_name, spanContext, propagationContext, baggage, sentryTrace) => { - const context = trace.setSpanContext( - setPropagationContextOnContext(ROOT_CONTEXT, propagationContext), - spanContext, - ); - propagator.inject(context, carrier, defaultTextMapSetter); - expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(baggage.sort()); - expect(carrier[SENTRY_TRACE_HEADER]).toBe(sentryTrace); + ])('%s', (_name, spanContext, baggage, sentryTrace) => { + context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => { + trace.getTracer('test').startActiveSpan('test', span => { + propagator.inject(context.active(), carrier, defaultTextMapSetter); + + expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual(baggage.sort()); + expect(carrier[SENTRY_TRACE_HEADER]).toBe(sentryTrace.replace('{{spanId}}', span.spanContext().spanId)); + }); + }); }); - it('should include existing baggage', () => { - const propagationContext: PropagationContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - parentSpanId: '6e0c63257de34c93', - sampled: true, - dsc: { - transaction: 'sampled-transaction', - trace_id: 'd4cda95b652f4a1592b449d5929fda1b', - sampled: 'true', - public_key: 'abc', - environment: 'production', - release: '1.0.0', + it('uses local span over propagation context', () => { + context.with( + trace.setSpanContext(ROOT_CONTEXT, { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + isRemote: true, + }), + () => { + trace.getTracer('test').startActiveSpan('test', span => { + withScope(scope => { + scope.setPropagationContext({ + traceId: 'TRACE_ID', + parentSpanId: 'PARENT_SPAN_ID', + spanId: 'SPAN_ID', + sampled: true, + }); + + propagator.inject(context.active(), carrier, defaultTextMapSetter); + + expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual( + [ + 'sentry-environment=production', + 'sentry-release=1.0.0', + 'sentry-public_key=abc', + 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', + ].sort(), + ); + expect(carrier[SENTRY_TRACE_HEADER]).toBe( + `d4cda95b652f4a1592b449d5929fda1b-${span.spanContext().spanId}-1`, + ); + }); + }); }, - }; - - const spanContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }; - const context = trace.setSpanContext( - setPropagationContextOnContext(ROOT_CONTEXT, propagationContext), - spanContext, ); - const baggage = propagation.createBaggage({ foo: { value: 'bar' } }); - propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter); - expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual( - [ - 'foo=bar', - 'sentry-transaction=sampled-transaction', - 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - 'sentry-sampled=true', - 'sentry-public_key=abc', - 'sentry-environment=production', - 'sentry-release=1.0.0', - ].sort(), - ); - }); - it('should create baggage without propagation context', () => { - const spanContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }; - const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); - const baggage = propagation.createBaggage({ foo: { value: 'bar' } }); - propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter); - expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe('foo=bar'); - }); + context.with( + trace.setSpanContext(ROOT_CONTEXT, { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.NONE, + isRemote: true, + }), + () => { + withScope(scope => { + scope.setPropagationContext({ + traceId: 'TRACE_ID', + parentSpanId: 'PARENT_SPAN_ID', + spanId: 'SPAN_ID', + sampled: true, + }); - it('should NOT set baggage and sentry-trace header if instrumentation is supressed', () => { - const spanContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - traceFlags: TraceFlags.SAMPLED, - }; - const propagationContext: PropagationContext = { - traceId: 'd4cda95b652f4a1592b449d5929fda1b', - spanId: '6e0c63257de34c92', - parentSpanId: '6e0c63257de34c93', - sampled: true, - dsc: { - transaction: 'sampled-transaction', - trace_id: 'd4cda95b652f4a1592b449d5929fda1b', - sampled: 'true', - public_key: 'abc', - environment: 'production', - release: '1.0.0', + propagator.inject(context.active(), carrier, defaultTextMapSetter); + + expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual( + [ + 'sentry-environment=production', + 'sentry-release=1.0.0', + 'sentry-public_key=abc', + 'sentry-trace_id=TRACE_ID', + ].sort(), + ); + expect(carrier[SENTRY_TRACE_HEADER]).toBe('TRACE_ID-SPAN_ID-1'); + }); }, - }; - const context = suppressTracing( - trace.setSpanContext(setPropagationContextOnContext(ROOT_CONTEXT, propagationContext), spanContext), ); - propagator.inject(context, carrier, defaultTextMapSetter); - expect(carrier[SENTRY_TRACE_HEADER]).toBe(undefined); - expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(undefined); }); }); - it('should take span from propagationContext id if no active span is found', () => { - const propagationContext: PropagationContext = { + it('should include existing baggage', () => { + const spanContext = { traceId: 'd4cda95b652f4a1592b449d5929fda1b', - parentSpanId: '6e0c63257de34c93', spanId: '6e0c63257de34c92', - sampled: true, - dsc: { - transaction: 'sampled-transaction', - trace_id: 'd4cda95b652f4a1592b449d5929fda1b', - sampled: 'true', - public_key: 'abc', - environment: 'production', - release: '1.0.0', - }, + traceFlags: TraceFlags.SAMPLED, }; - - const context = setPropagationContextOnContext(ROOT_CONTEXT, propagationContext); - propagator.inject(context, carrier, defaultTextMapSetter); + const context = trace.setSpanContext(ROOT_CONTEXT, spanContext); + const baggage = propagation.createBaggage({ foo: { value: 'bar' } }); + propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter); expect(baggageToArray(carrier[SENTRY_BAGGAGE_HEADER])).toEqual( [ - 'sentry-transaction=sampled-transaction', + 'foo=bar', 'sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b', - 'sentry-sampled=true', 'sentry-public_key=abc', 'sentry-environment=production', 'sentry-release=1.0.0', ].sort(), ); - expect(carrier[SENTRY_TRACE_HEADER]).toBe('d4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'); + }); + + it('should create baggage without propagation context', () => { + const context = ROOT_CONTEXT; + const baggage = propagation.createBaggage({ foo: { value: 'bar' } }); + propagator.inject(propagation.setBaggage(context, baggage), carrier, defaultTextMapSetter); + expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe('foo=bar'); + }); + + it('should NOT set baggage and sentry-trace header if instrumentation is supressed', () => { + const spanContext = { + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + }; + + const context = suppressTracing(trace.setSpanContext(ROOT_CONTEXT, spanContext)); + propagator.inject(context, carrier, defaultTextMapSetter); + expect(carrier[SENTRY_TRACE_HEADER]).toBe(undefined); + expect(carrier[SENTRY_BAGGAGE_HEADER]).toBe(undefined); }); }); describe('extract', () => { - it('sets sentry span context on the context', () => { + it('sets data from sentry trace header on span context', () => { const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'; carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); @@ -322,66 +498,98 @@ describe('SentryPropagator', () => { spanId: '6e0c63257de34c92', traceFlags: TraceFlags.SAMPLED, traceId: 'd4cda95b652f4a1592b449d5929fda1b', + traceState: makeTraceState({ parentSpanId: '6e0c63257de34c92' }), }); }); - it('sets defined sentry trace header on context', () => { + it('sets data from sentry trace header on scope', () => { const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'; carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - const propagationContext = getPropagationContextFromContext(context); - expect(propagationContext).toEqual({ - sampled: true, - parentSpanId: '6e0c63257de34c92', + const scopes = getScopesFromContext(context); + + expect(scopes).toBeDefined(); + expect(scopes?.scope.getPropagationContext()).toEqual({ spanId: expect.any(String), + sampled: true, traceId: 'd4cda95b652f4a1592b449d5929fda1b', - dsc: {}, // Frozen DSC + parentSpanId: '6e0c63257de34c92', + dsc: {}, }); - - // Ensure spanId !== parentSpanId - it should be a new random ID - expect(propagationContext?.spanId).not.toBe('6e0c63257de34c92'); }); - it('sets undefined sentry trace header on context', () => { + it('handles undefined sentry trace header', () => { const sentryTraceHeader = undefined; carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(getPropagationContextFromContext(context)).toEqual({ + expect(trace.getSpanContext(context)).toEqual({ + isRemote: true, spanId: expect.any(String), + traceFlags: TraceFlags.NONE, traceId: expect.any(String), }); }); - it('sets defined dynamic sampling context on context', () => { + it('sets data from baggage header on span context', () => { const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'; const baggage = 'sentry-environment=production,sentry-release=1.0.0,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b,sentry-transaction=dsc-transaction'; carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; carrier[SENTRY_BAGGAGE_HEADER] = baggage; const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(getPropagationContextFromContext(context)).toEqual({ - sampled: true, - parentSpanId: expect.any(String), + expect(trace.getSpanContext(context)).toEqual({ + isRemote: true, + spanId: '6e0c63257de34c92', + traceFlags: TraceFlags.SAMPLED, + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + traceState: makeTraceState({ + parentSpanId: '6e0c63257de34c92', + dsc: { + environment: 'production', + release: '1.0.0', + public_key: 'abc', + trace_id: 'd4cda95b652f4a1592b449d5929fda1b', + transaction: 'dsc-transaction', + }, + }), + }); + }); + + it('sets data from baggage header on scope', () => { + const sentryTraceHeader = 'd4cda95b652f4a1592b449d5929fda1b-6e0c63257de34c92-1'; + const baggage = + 'sentry-environment=production,sentry-release=1.0.0,sentry-public_key=abc,sentry-trace_id=d4cda95b652f4a1592b449d5929fda1b,sentry-transaction=dsc-transaction'; + carrier[SENTRY_TRACE_HEADER] = sentryTraceHeader; + carrier[SENTRY_BAGGAGE_HEADER] = baggage; + const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); + + const scopes = getScopesFromContext(context); + + expect(scopes).toBeDefined(); + expect(scopes?.scope.getPropagationContext()).toEqual({ spanId: expect.any(String), - traceId: expect.any(String), // Note: This is not automatically taken from the DSC (in reality, this should be aligned) + sampled: true, + traceId: 'd4cda95b652f4a1592b449d5929fda1b', + parentSpanId: '6e0c63257de34c92', dsc: { environment: 'production', - public_key: 'abc', release: '1.0.0', + public_key: 'abc', trace_id: 'd4cda95b652f4a1592b449d5929fda1b', transaction: 'dsc-transaction', }, }); }); - it('sets undefined dynamic sampling context on context', () => { + it('handles empty dsc baggage header', () => { const baggage = ''; carrier[SENTRY_BAGGAGE_HEADER] = baggage; const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(getPropagationContextFromContext(context)).toEqual({ - sampled: undefined, + expect(trace.getSpanContext(context)).toEqual({ + isRemote: true, spanId: expect.any(String), + traceFlags: TraceFlags.NONE, traceId: expect.any(String), }); }); @@ -389,9 +597,10 @@ describe('SentryPropagator', () => { it('handles when sentry-trace is an empty array', () => { carrier[SENTRY_TRACE_HEADER] = []; const context = propagator.extract(ROOT_CONTEXT, carrier, defaultTextMapGetter); - expect(getPropagationContextFromContext(context)).toEqual({ - sampled: undefined, + expect(trace.getSpanContext(context)).toEqual({ + isRemote: true, spanId: expect.any(String), + traceFlags: TraceFlags.NONE, traceId: expect.any(String), }); }); diff --git a/packages/opentelemetry/test/trace.test.ts b/packages/opentelemetry/test/trace.test.ts index 126586d29538..2a6b9f6c3426 100644 --- a/packages/opentelemetry/test/trace.test.ts +++ b/packages/opentelemetry/test/trace.test.ts @@ -1,4 +1,5 @@ import type { Span, TimeInput } from '@opentelemetry/api'; +import { ROOT_CONTEXT } from '@opentelemetry/api'; import { SpanKind } from '@opentelemetry/api'; import { TraceFlags, context, trace } from '@opentelemetry/api'; import type { ReadableSpan } from '@opentelemetry/sdk-trace-base'; @@ -10,13 +11,12 @@ import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getClient, getCurrentScope, - spanToJSON, + withScope, } from '@sentry/core'; -import type { Event, PropagationContext, Scope } from '@sentry/types'; +import type { Event, Scope } from '@sentry/types'; import { startInactiveSpan, startSpan, startSpanManual } from '../src/trace'; import type { AbstractSpan } from '../src/types'; -import { setPropagationContextOnContext } from '../src/utils/contextData'; import { getActiveSpan, getRootSpan } from '../src/utils/getActiveSpan'; import { getSpanKind } from '../src/utils/getSpanKind'; import { getSpanMetadata } from '../src/utils/spanData'; @@ -336,7 +336,7 @@ describe('trace', () => { const normalizedTransactionEvents = transactionEvents.map(event => { return { ...event, - spans: event.spans?.map(span => ({ name: spanToJSON(span).description, id: span.spanContext().spanId })), + spans: event.spans?.map(span => ({ name: span.description, id: span.span_id })), }; }); @@ -369,9 +369,7 @@ describe('trace', () => { status: 'ok', }); expect(outerTransaction?.spans).toEqual([{ name: 'inner span', id: expect.any(String) }]); - expect(outerTransaction?.tags).toEqual({ - transaction: 'outer transaction', - }); + expect(outerTransaction?.transaction).toEqual('outer transaction'); expect(outerTransaction?.sdkProcessingMetadata).toEqual({ dynamicSamplingContext: { environment: 'production', @@ -397,9 +395,7 @@ describe('trace', () => { status: 'ok', }); expect(innerTransaction?.spans).toEqual([{ name: 'inner span 2', id: expect.any(String) }]); - expect(innerTransaction?.tags).toEqual({ - transaction: 'inner transaction', - }); + expect(innerTransaction?.transaction).toEqual('inner transaction'); expect(innerTransaction?.sdkProcessingMetadata).toEqual({ dynamicSamplingContext: { environment: 'production', @@ -582,7 +578,7 @@ describe('trace', () => { const normalizedTransactionEvents = transactionEvents.map(event => { return { ...event, - spans: event.spans?.map(span => ({ name: spanToJSON(span).description, id: span.spanContext().spanId })), + spans: event.spans?.map(span => ({ name: span.description, id: span.span_id })), }; }); @@ -615,9 +611,7 @@ describe('trace', () => { status: 'ok', }); expect(outerTransaction?.spans).toEqual([{ name: 'inner span', id: expect.any(String) }]); - expect(outerTransaction?.tags).toEqual({ - transaction: 'outer transaction', - }); + expect(outerTransaction?.transaction).toEqual('outer transaction'); expect(outerTransaction?.sdkProcessingMetadata).toEqual({ dynamicSamplingContext: { environment: 'production', @@ -643,9 +637,7 @@ describe('trace', () => { status: 'ok', }); expect(innerTransaction?.spans).toEqual([]); - expect(innerTransaction?.tags).toEqual({ - transaction: 'inner transaction', - }); + expect(innerTransaction?.transaction).toEqual('inner transaction'); expect(innerTransaction?.sdkProcessingMetadata).toEqual({ dynamicSamplingContext: { environment: 'production', @@ -675,6 +667,44 @@ describe('trace', () => { expect(span).toBeInstanceOf(SpanClass); }); }); + + it('includes the scope at the time the span was started when finished', async () => { + const beforeSendTransaction = jest.fn(event => event); + + const client = getClient()!; + + client.getOptions().beforeSendTransaction = beforeSendTransaction; + + let span: Span | undefined; + + const scope = getCurrentScope(); + scope.setTag('outer', 'foo'); + + withScope(scope => { + scope.setTag('scope', 1); + span = startInactiveSpan({ name: 'my-span' }); + scope.setTag('scope_after_span', 2); + }); + + withScope(scope => { + scope.setTag('scope', 2); + span?.end(); + }); + + await client.flush(); + + expect(beforeSendTransaction).toHaveBeenCalledTimes(1); + expect(beforeSendTransaction).toHaveBeenCalledWith( + expect.objectContaining({ + tags: expect.objectContaining({ + outer: 'foo', + scope: 1, + scope_after_span: 2, + }), + }), + expect.anything(), + ); + }); }); describe('startSpanManual', () => { @@ -819,7 +849,7 @@ describe('trace', () => { const normalizedTransactionEvents = transactionEvents.map(event => { return { ...event, - spans: event.spans?.map(span => ({ name: spanToJSON(span).description, id: span.spanContext().spanId })), + spans: event.spans?.map(span => ({ name: span.description, id: span.span_id })), }; }); @@ -852,9 +882,7 @@ describe('trace', () => { status: 'ok', }); expect(outerTransaction?.spans).toEqual([{ name: 'inner span', id: expect.any(String) }]); - expect(outerTransaction?.tags).toEqual({ - transaction: 'outer transaction', - }); + expect(outerTransaction?.transaction).toEqual('outer transaction'); expect(outerTransaction?.sdkProcessingMetadata).toEqual({ dynamicSamplingContext: { environment: 'production', @@ -880,9 +908,7 @@ describe('trace', () => { status: 'ok', }); expect(innerTransaction?.spans).toEqual([{ name: 'inner span 2', id: expect.any(String) }]); - expect(innerTransaction?.tags).toEqual({ - transaction: 'inner transaction', - }); + expect(innerTransaction?.transaction).toEqual('inner transaction'); expect(innerTransaction?.sdkProcessingMetadata).toEqual({ dynamicSamplingContext: { environment: 'production', @@ -1050,25 +1076,15 @@ describe('trace (sampling)', () => { traceFlags: TraceFlags.SAMPLED, }; - const propagationContext: PropagationContext = { - traceId, - sampled: true, - parentSpanId, - spanId: '6e0c63257de34c93', - }; - // We simulate the correct context we'd normally get from the SentryPropagator - context.with( - trace.setSpanContext(setPropagationContextOnContext(context.active(), propagationContext), spanContext), - () => { - // This will def. be sampled because of the tracesSampleRate - startSpan({ name: 'outer' }, outerSpan => { - expect(outerSpan).toBeDefined(); - expect(outerSpan.isRecording()).toBe(true); - expect(getSpanName(outerSpan)).toBe('outer'); - }); - }, - ); + context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => { + // This will def. be sampled because of the tracesSampleRate + startSpan({ name: 'outer' }, outerSpan => { + expect(outerSpan).toBeDefined(); + expect(outerSpan.isRecording()).toBe(true); + expect(getSpanName(outerSpan)).toBe('outer'); + }); + }); }); it('negative remote parent sampling takes precedence over tracesSampleRate', () => { @@ -1087,24 +1103,14 @@ describe('trace (sampling)', () => { traceFlags: TraceFlags.NONE, }; - const propagationContext: PropagationContext = { - traceId, - sampled: false, - parentSpanId, - spanId: '6e0c63257de34c93', - }; - // We simulate the correct context we'd normally get from the SentryPropagator - context.with( - trace.setSpanContext(setPropagationContextOnContext(context.active(), propagationContext), spanContext), - () => { - // This will def. be sampled because of the tracesSampleRate - startSpan({ name: 'outer' }, outerSpan => { - expect(outerSpan).toBeDefined(); - expect(outerSpan.isRecording()).toBe(false); - }); - }, - ); + context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => { + // This will def. be sampled because of the tracesSampleRate + startSpan({ name: 'outer' }, outerSpan => { + expect(outerSpan).toBeDefined(); + expect(outerSpan.isRecording()).toBe(false); + }); + }); }); it('samples with a tracesSampler returning a boolean', () => { @@ -1227,23 +1233,13 @@ describe('trace (sampling)', () => { traceFlags: TraceFlags.SAMPLED, }; - const propagationContext: PropagationContext = { - traceId, - sampled: true, - parentSpanId, - spanId: '6e0c63257de34c93', - }; - // We simulate the correct context we'd normally get from the SentryPropagator - context.with( - trace.setSpanContext(setPropagationContextOnContext(context.active(), propagationContext), spanContext), - () => { - // This will def. be sampled because of the tracesSampleRate - startSpan({ name: 'outer' }, outerSpan => { - expect(outerSpan.isRecording()).toBe(false); - }); - }, - ); + context.with(trace.setSpanContext(ROOT_CONTEXT, spanContext), () => { + // This will def. be sampled because of the tracesSampleRate + startSpan({ name: 'outer' }, outerSpan => { + expect(outerSpan.isRecording()).toBe(false); + }); + }); expect(tracesSampler).toBeCalledTimes(1); expect(tracesSampler).toHaveBeenLastCalledWith({ diff --git a/packages/opentelemetry/test/utils/mapStatus.test.ts b/packages/opentelemetry/test/utils/mapStatus.test.ts index 4fa6cc664b61..eb9182973d23 100644 --- a/packages/opentelemetry/test/utils/mapStatus.test.ts +++ b/packages/opentelemetry/test/utils/mapStatus.test.ts @@ -1,65 +1,66 @@ import { SemanticAttributes } from '@opentelemetry/semantic-conventions'; -import type { SpanStatusType } from '@sentry/core'; +import { SPAN_STATUS_ERROR, SPAN_STATUS_OK } from '@sentry/core'; +import type { SpanStatus } from '@sentry/types'; import { mapStatus } from '../../src/utils/mapStatus'; import { createSpan } from '../helpers/createSpan'; describe('mapStatus', () => { - const statusTestTable: [number, undefined | number | string, undefined | string, SpanStatusType][] = [ - [-1, undefined, undefined, 'unknown_error'], - [3, undefined, undefined, 'unknown_error'], - [0, undefined, undefined, 'ok'], - [1, undefined, undefined, 'ok'], - [2, undefined, undefined, 'unknown_error'], + const statusTestTable: [number, undefined | number | string, undefined | string, SpanStatus][] = [ + [-1, undefined, undefined, { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], + [3, undefined, undefined, { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], + [0, undefined, undefined, { code: SPAN_STATUS_OK }], + [1, undefined, undefined, { code: SPAN_STATUS_OK }], + [2, undefined, undefined, { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], // http codes - [2, 400, undefined, 'failed_precondition'], - [2, 401, undefined, 'unauthenticated'], - [2, 403, undefined, 'permission_denied'], - [2, 404, undefined, 'not_found'], - [2, 409, undefined, 'aborted'], - [2, 429, undefined, 'resource_exhausted'], - [2, 499, undefined, 'cancelled'], - [2, 500, undefined, 'internal_error'], - [2, 501, undefined, 'unimplemented'], - [2, 503, undefined, 'unavailable'], - [2, 504, undefined, 'deadline_exceeded'], - [2, 999, undefined, 'unknown_error'], + [2, 400, undefined, { code: SPAN_STATUS_ERROR, message: 'failed_precondition' }], + [2, 401, undefined, { code: SPAN_STATUS_ERROR, message: 'unauthenticated' }], + [2, 403, undefined, { code: SPAN_STATUS_ERROR, message: 'permission_denied' }], + [2, 404, undefined, { code: SPAN_STATUS_ERROR, message: 'not_found' }], + [2, 409, undefined, { code: SPAN_STATUS_ERROR, message: 'aborted' }], + [2, 429, undefined, { code: SPAN_STATUS_ERROR, message: 'resource_exhausted' }], + [2, 499, undefined, { code: SPAN_STATUS_ERROR, message: 'cancelled' }], + [2, 500, undefined, { code: SPAN_STATUS_ERROR, message: 'internal_error' }], + [2, 501, undefined, { code: SPAN_STATUS_ERROR, message: 'unimplemented' }], + [2, 503, undefined, { code: SPAN_STATUS_ERROR, message: 'unavailable' }], + [2, 504, undefined, { code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' }], + [2, 999, undefined, { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], - [2, '400', undefined, 'failed_precondition'], - [2, '401', undefined, 'unauthenticated'], - [2, '403', undefined, 'permission_denied'], - [2, '404', undefined, 'not_found'], - [2, '409', undefined, 'aborted'], - [2, '429', undefined, 'resource_exhausted'], - [2, '499', undefined, 'cancelled'], - [2, '500', undefined, 'internal_error'], - [2, '501', undefined, 'unimplemented'], - [2, '503', undefined, 'unavailable'], - [2, '504', undefined, 'deadline_exceeded'], - [2, '999', undefined, 'unknown_error'], + [2, '400', undefined, { code: SPAN_STATUS_ERROR, message: 'failed_precondition' }], + [2, '401', undefined, { code: SPAN_STATUS_ERROR, message: 'unauthenticated' }], + [2, '403', undefined, { code: SPAN_STATUS_ERROR, message: 'permission_denied' }], + [2, '404', undefined, { code: SPAN_STATUS_ERROR, message: 'not_found' }], + [2, '409', undefined, { code: SPAN_STATUS_ERROR, message: 'aborted' }], + [2, '429', undefined, { code: SPAN_STATUS_ERROR, message: 'resource_exhausted' }], + [2, '499', undefined, { code: SPAN_STATUS_ERROR, message: 'cancelled' }], + [2, '500', undefined, { code: SPAN_STATUS_ERROR, message: 'internal_error' }], + [2, '501', undefined, { code: SPAN_STATUS_ERROR, message: 'unimplemented' }], + [2, '503', undefined, { code: SPAN_STATUS_ERROR, message: 'unavailable' }], + [2, '504', undefined, { code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' }], + [2, '999', undefined, { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], // grpc codes - [2, undefined, '1', 'cancelled'], - [2, undefined, '2', 'unknown_error'], - [2, undefined, '3', 'invalid_argument'], - [2, undefined, '4', 'deadline_exceeded'], - [2, undefined, '5', 'not_found'], - [2, undefined, '6', 'already_exists'], - [2, undefined, '7', 'permission_denied'], - [2, undefined, '8', 'resource_exhausted'], - [2, undefined, '9', 'failed_precondition'], - [2, undefined, '10', 'aborted'], - [2, undefined, '11', 'out_of_range'], - [2, undefined, '12', 'unimplemented'], - [2, undefined, '13', 'internal_error'], - [2, undefined, '14', 'unavailable'], - [2, undefined, '15', 'data_loss'], - [2, undefined, '16', 'unauthenticated'], - [2, undefined, '999', 'unknown_error'], + [2, undefined, '1', { code: SPAN_STATUS_ERROR, message: 'cancelled' }], + [2, undefined, '2', { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], + [2, undefined, '3', { code: SPAN_STATUS_ERROR, message: 'invalid_argument' }], + [2, undefined, '4', { code: SPAN_STATUS_ERROR, message: 'deadline_exceeded' }], + [2, undefined, '5', { code: SPAN_STATUS_ERROR, message: 'not_found' }], + [2, undefined, '6', { code: SPAN_STATUS_ERROR, message: 'already_exists' }], + [2, undefined, '7', { code: SPAN_STATUS_ERROR, message: 'permission_denied' }], + [2, undefined, '8', { code: SPAN_STATUS_ERROR, message: 'resource_exhausted' }], + [2, undefined, '9', { code: SPAN_STATUS_ERROR, message: 'failed_precondition' }], + [2, undefined, '10', { code: SPAN_STATUS_ERROR, message: 'aborted' }], + [2, undefined, '11', { code: SPAN_STATUS_ERROR, message: 'out_of_range' }], + [2, undefined, '12', { code: SPAN_STATUS_ERROR, message: 'unimplemented' }], + [2, undefined, '13', { code: SPAN_STATUS_ERROR, message: 'internal_error' }], + [2, undefined, '14', { code: SPAN_STATUS_ERROR, message: 'unavailable' }], + [2, undefined, '15', { code: SPAN_STATUS_ERROR, message: 'data_loss' }], + [2, undefined, '16', { code: SPAN_STATUS_ERROR, message: 'unauthenticated' }], + [2, undefined, '999', { code: SPAN_STATUS_ERROR, message: 'unknown_error' }], // http takes precedence over grpc - [2, '400', '2', 'failed_precondition'], + [2, '400', '2', { code: SPAN_STATUS_ERROR, message: 'failed_precondition' }], ]; it.each(statusTestTable)( diff --git a/packages/opentelemetry/test/utils/setupCheck.test.ts b/packages/opentelemetry/test/utils/setupCheck.test.ts new file mode 100644 index 000000000000..86eca3688731 --- /dev/null +++ b/packages/opentelemetry/test/utils/setupCheck.test.ts @@ -0,0 +1,44 @@ +import { BasicTracerProvider } from '@opentelemetry/sdk-trace-base'; + +import { SentrySampler } from '../../src/sampler'; +import { SentrySpanProcessor } from '../../src/spanProcessor'; +import { openTelemetrySetupCheck } from '../../src/utils/setupCheck'; +import { TestClient, getDefaultTestClientOptions } from '../helpers/TestClient'; +import { setupOtel } from '../helpers/initOtel'; +import { cleanupOtel } from '../helpers/mockSdkInit'; + +describe('openTelemetrySetupCheck', () => { + let provider: BasicTracerProvider | undefined; + + beforeEach(() => { + cleanupOtel(provider); + }); + + afterEach(() => { + cleanupOtel(provider); + }); + + it('returns empty array by default', () => { + const setup = openTelemetrySetupCheck(); + expect(setup).toEqual([]); + }); + + it('returns all setup parts', () => { + const client = new TestClient(getDefaultTestClientOptions()); + provider = setupOtel(client); + + const setup = openTelemetrySetupCheck(); + expect(setup).toEqual(['SentrySampler', 'SentrySpanProcessor', 'SentryPropagator', 'SentryContextManager']); + }); + + it('returns partial setup parts', () => { + const client = new TestClient(getDefaultTestClientOptions()); + provider = new BasicTracerProvider({ + sampler: new SentrySampler(client), + }); + provider.addSpanProcessor(new SentrySpanProcessor()); + + const setup = openTelemetrySetupCheck(); + expect(setup).toEqual(['SentrySampler', 'SentrySpanProcessor']); + }); +}); diff --git a/packages/profiling-node/test/hubextensions.hub.test.ts b/packages/profiling-node/test/hubextensions.hub.test.ts index 755923f62af7..2266e5cec610 100644 --- a/packages/profiling-node/test/hubextensions.hub.test.ts +++ b/packages/profiling-node/test/hubextensions.hub.test.ts @@ -98,7 +98,7 @@ describe('hubextensions', () => { // eslint-disable-next-line deprecation/deprecation hub.bindClient(client); - const transportSpy = jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve()); + const transportSpy = jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); // eslint-disable-next-line deprecation/deprecation const transaction = Sentry.getCurrentHub().startTransaction({ name: 'profile_hub' }); @@ -135,7 +135,7 @@ describe('hubextensions', () => { }; }); - jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve()); + jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); // eslint-disable-next-line deprecation/deprecation const transaction = Sentry.getCurrentHub().startTransaction({ name: 'profile_hub' }); @@ -183,7 +183,7 @@ describe('hubextensions', () => { }; }); - jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve()); + jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); // eslint-disable-next-line deprecation/deprecation const transaction = Sentry.getCurrentHub().startTransaction({ name: 'profile_hub', traceId: 'boop' }); @@ -205,7 +205,7 @@ describe('hubextensions', () => { const startProfilingSpy = jest.spyOn(CpuProfilerBindings, 'startProfiling'); const stopProfilingSpy = jest.spyOn(CpuProfilerBindings, 'stopProfiling'); - jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve()); + jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); // eslint-disable-next-line deprecation/deprecation const transaction = hub.startTransaction({ name: 'profile_hub' }); @@ -225,7 +225,7 @@ describe('hubextensions', () => { // eslint-disable-next-line deprecation/deprecation hub.bindClient(client); - const transportSpy = jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve()); + const transportSpy = jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); // eslint-disable-next-line deprecation/deprecation const transaction = hub.startTransaction({ name: 'profile_hub' }); @@ -276,7 +276,7 @@ describe('hubextensions', () => { // Emit is sync, so we can just assert that we got here const transportSpy = jest.spyOn(transport, 'send').mockImplementation(() => { // Do nothing so we don't send events to Sentry - return Promise.resolve(); + return Promise.resolve({}); }); // eslint-disable-next-line deprecation/deprecation @@ -348,7 +348,7 @@ describe('hubextensions', () => { const transportSpy = jest.spyOn(transport, 'send').mockImplementation(() => { // Do nothing so we don't send events to Sentry - return Promise.resolve(); + return Promise.resolve({}); }); // eslint-disable-next-line deprecation/deprecation @@ -441,7 +441,7 @@ describe('hubextensions', () => { // eslint-disable-next-line deprecation/deprecation hub.bindClient(client); - const transportSpy = jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve()); + const transportSpy = jest.spyOn(transport, 'send').mockReturnValue(Promise.resolve({})); // eslint-disable-next-line deprecation/deprecation const transaction = hub.startTransaction({ name: 'profile_hub' }); diff --git a/packages/profiling-node/test/index.test.ts b/packages/profiling-node/test/index.test.ts index 3a59d4bd53f4..0a074226f692 100644 --- a/packages/profiling-node/test/index.test.ts +++ b/packages/profiling-node/test/index.test.ts @@ -14,7 +14,7 @@ function makeStaticTransport(): MockTransport { events: [] as any[], send: function (...args: any[]) { this.events.push(args); - return Promise.resolve(); + return Promise.resolve({}); }, flush: function () { return Promise.resolve(true); diff --git a/packages/react/src/profiler.tsx b/packages/react/src/profiler.tsx index 54ed3fa541c8..5008ffdc0010 100644 --- a/packages/react/src/profiler.tsx +++ b/packages/react/src/profiler.tsx @@ -1,5 +1,5 @@ import { startInactiveSpan } from '@sentry/browser'; -import { spanToJSON, withActiveSpan } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, spanToJSON, withActiveSpan } from '@sentry/core'; import type { Span } from '@sentry/types'; import { timestampInSeconds } from '@sentry/utils'; import hoistNonReactStatics from 'hoist-non-react-statics'; @@ -59,8 +59,10 @@ class Profiler extends React.Component { name: `<${name}>`, onlyIfParent: true, op: REACT_MOUNT_OP, - origin: 'auto.ui.react.profiler', - attributes: { 'ui.component_name': name }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler', + 'ui.component_name': name, + }, }); } @@ -86,9 +88,9 @@ class Profiler extends React.Component { name: `<${this.props.name}>`, onlyIfParent: true, op: REACT_UPDATE_OP, - origin: 'auto.ui.react.profiler', - startTimestamp: now, + startTime: now, attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler', 'ui.component_name': this.props.name, 'ui.react.changed_props': changedProps, }, @@ -114,15 +116,17 @@ class Profiler extends React.Component { const { name, includeRender = true } = this.props; if (this._mountSpan && includeRender) { - const startTimestamp = spanToJSON(this._mountSpan).timestamp; + const startTime = spanToJSON(this._mountSpan).timestamp; withActiveSpan(this._mountSpan, () => { const renderSpan = startInactiveSpan({ onlyIfParent: true, name: `<${name}>`, op: REACT_RENDER_OP, - origin: 'auto.ui.react.profiler', - startTimestamp, - attributes: { 'ui.component_name': name }, + startTime, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler', + 'ui.component_name': name, + }, }); if (renderSpan) { // Have to cast to Span because the type of _mountSpan is Span | undefined @@ -192,8 +196,10 @@ function useProfiler( name: `<${name}>`, onlyIfParent: true, op: REACT_MOUNT_OP, - origin: 'auto.ui.react.profiler', - attributes: { 'ui.component_name': name }, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler', + 'ui.component_name': name, + }, }); }); @@ -204,16 +210,18 @@ function useProfiler( return (): void => { if (mountSpan && options.hasRenderSpan) { - const startTimestamp = spanToJSON(mountSpan).timestamp; + const startTime = spanToJSON(mountSpan).timestamp; const endTimestamp = timestampInSeconds(); const renderSpan = startInactiveSpan({ name: `<${name}>`, onlyIfParent: true, op: REACT_RENDER_OP, - origin: 'auto.ui.react.profiler', - startTimestamp, - attributes: { 'ui.component_name': name }, + startTime, + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.react.profiler', + 'ui.component_name': name, + }, }); if (renderSpan) { // Have to cast to Span because the type of _mountSpan is Span | undefined diff --git a/packages/react/test/profiler.test.tsx b/packages/react/test/profiler.test.tsx index de770b362473..9f8b23063e00 100644 --- a/packages/react/test/profiler.test.tsx +++ b/packages/react/test/profiler.test.tsx @@ -74,8 +74,10 @@ describe('withProfiler', () => { name: `<${UNKNOWN_COMPONENT}>`, onlyIfParent: true, op: REACT_MOUNT_OP, - origin: 'auto.ui.react.profiler', - attributes: { 'ui.component_name': 'unknown' }, + attributes: { + 'sentry.origin': 'auto.ui.react.profiler', + 'ui.component_name': 'unknown', + }, }); }); }); @@ -93,9 +95,11 @@ describe('withProfiler', () => { name: `<${UNKNOWN_COMPONENT}>`, onlyIfParent: true, op: REACT_RENDER_OP, - origin: 'auto.ui.react.profiler', - startTimestamp: undefined, - attributes: { 'ui.component_name': 'unknown' }, + startTime: undefined, + attributes: { + 'sentry.origin': 'auto.ui.react.profiler', + 'ui.component_name': 'unknown', + }, }); expect(mockFinish).toHaveBeenCalledTimes(2); }); @@ -123,24 +127,30 @@ describe('withProfiler', () => { rerender(); expect(mockStartInactiveSpan).toHaveBeenCalledTimes(2); expect(mockStartInactiveSpan).toHaveBeenLastCalledWith({ - attributes: { 'ui.react.changed_props': ['num'], 'ui.component_name': 'unknown' }, + attributes: { + 'sentry.origin': 'auto.ui.react.profiler', + 'ui.react.changed_props': ['num'], + 'ui.component_name': 'unknown', + }, name: `<${UNKNOWN_COMPONENT}>`, onlyIfParent: true, op: REACT_UPDATE_OP, - origin: 'auto.ui.react.profiler', - startTimestamp: expect.any(Number), + startTime: expect.any(Number), }); expect(mockFinish).toHaveBeenCalledTimes(2); // New props yet again rerender(); expect(mockStartInactiveSpan).toHaveBeenCalledTimes(3); expect(mockStartInactiveSpan).toHaveBeenLastCalledWith({ - attributes: { 'ui.react.changed_props': ['num'], 'ui.component_name': 'unknown' }, + attributes: { + 'sentry.origin': 'auto.ui.react.profiler', + 'ui.react.changed_props': ['num'], + 'ui.component_name': 'unknown', + }, name: `<${UNKNOWN_COMPONENT}>`, onlyIfParent: true, op: REACT_UPDATE_OP, - origin: 'auto.ui.react.profiler', - startTimestamp: expect.any(Number), + startTime: expect.any(Number), }); expect(mockFinish).toHaveBeenCalledTimes(3); @@ -179,8 +189,10 @@ describe('useProfiler()', () => { name: '', onlyIfParent: true, op: REACT_MOUNT_OP, - origin: 'auto.ui.react.profiler', - attributes: { 'ui.component_name': 'Example' }, + attributes: { + 'ui.component_name': 'Example', + 'sentry.origin': 'auto.ui.react.profiler', + }, }); }); }); @@ -204,8 +216,10 @@ describe('useProfiler()', () => { name: '', onlyIfParent: true, op: REACT_RENDER_OP, - origin: 'auto.ui.react.profiler', - attributes: { 'ui.component_name': 'Example' }, + attributes: { + 'sentry.origin': 'auto.ui.react.profiler', + 'ui.component_name': 'Example', + }, }), ); }); diff --git a/packages/remix/package.json b/packages/remix/package.json index c6e14a9c0a8e..b5bd70796199 100644 --- a/packages/remix/package.json +++ b/packages/remix/package.json @@ -35,7 +35,7 @@ }, "dependencies": { "@remix-run/router": "1.x", - "@sentry/cli": "^2.28.6", + "@sentry/cli": "^2.29.1", "@sentry/core": "8.0.0-alpha.0", "@sentry/node-experimental": "8.0.0-alpha.0", "@sentry/react": "8.0.0-alpha.0", diff --git a/packages/remix/src/client/performance.tsx b/packages/remix/src/client/performance.tsx index 1c2c83f6f052..fd997544bb8b 100644 --- a/packages/remix/src/client/performance.tsx +++ b/packages/remix/src/client/performance.tsx @@ -1,4 +1,9 @@ -import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, getActiveSpan, getRootSpan } from '@sentry/core'; +import { + SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + getActiveSpan, + getRootSpan, +} from '@sentry/core'; import type { browserTracingIntegration as originalBrowserTracingIntegration } from '@sentry/react'; import type { BrowserClient, ErrorBoundaryProps } from '@sentry/react'; import { @@ -72,8 +77,8 @@ export function startPageloadSpan(): void { const spanContext: StartSpanOptions = { name: initPathName, op: 'pageload', - origin: 'auto.pageload.remix', attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.remix', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url', }, }; @@ -96,8 +101,8 @@ function startNavigationSpan(matches: RouteMatch[]): void { const spanContext: StartSpanOptions = { name: matches[matches.length - 1].id, op: 'navigation', - origin: 'auto.navigation.remix', attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.navigation.remix', [SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'route', }, }; diff --git a/packages/remix/test/integration/test/client/capture-exception.test.ts b/packages/remix/test/integration/test/client/capture-exception.test.ts index e13136f59565..b7e38abf2f4c 100644 --- a/packages/remix/test/integration/test/client/capture-exception.test.ts +++ b/packages/remix/test/integration/test/client/capture-exception.test.ts @@ -8,7 +8,7 @@ test('should report a manually captured error.', async ({ page }) => { const [errorEnvelope, pageloadEnvelope] = envelopes; expect(errorEnvelope.level).toBe('error'); - expect(errorEnvelope.tags?.transaction).toBe('/capture-exception'); + expect(errorEnvelope.transaction).toBe('/capture-exception'); expect(errorEnvelope.exception?.values).toMatchObject([ { type: 'Error', @@ -18,7 +18,7 @@ test('should report a manually captured error.', async ({ page }) => { }, ]); - expect(pageloadEnvelope.contexts?.trace.op).toBe('pageload'); + expect(pageloadEnvelope.contexts?.trace?.op).toBe('pageload'); expect(pageloadEnvelope.type).toBe('transaction'); expect(pageloadEnvelope.transaction).toBe('routes/capture-exception'); }); diff --git a/packages/remix/test/integration/test/client/capture-message.test.ts b/packages/remix/test/integration/test/client/capture-message.test.ts index 2259c271565d..234b6ee3d961 100644 --- a/packages/remix/test/integration/test/client/capture-message.test.ts +++ b/packages/remix/test/integration/test/client/capture-message.test.ts @@ -8,10 +8,10 @@ test('should report a manually captured message.', async ({ page }) => { const [messageEnvelope, pageloadEnvelope] = envelopes; expect(messageEnvelope.level).toBe('info'); - expect(messageEnvelope.tags?.transaction).toBe('/capture-message'); + expect(messageEnvelope.transaction).toBe('/capture-message'); expect(messageEnvelope.message).toBe('Sentry Manually Captured Message'); - expect(pageloadEnvelope.contexts?.trace.op).toBe('pageload'); + expect(pageloadEnvelope.contexts?.trace?.op).toBe('pageload'); expect(pageloadEnvelope.type).toBe('transaction'); expect(pageloadEnvelope.transaction).toBe('routes/capture-message'); }); diff --git a/packages/remix/test/integration/test/server/action.test.ts b/packages/remix/test/integration/test/server/action.test.ts index 9c4c6effa828..fe0b4a749c43 100644 --- a/packages/remix/test/integration/test/server/action.test.ts +++ b/packages/remix/test/integration/test/server/action.test.ts @@ -162,9 +162,7 @@ describe.each(['builtin', 'express'])('Remix API Actions with adapter = %s', ada }, }, }, - tags: { - transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - }, + transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, }); assertSentryTransaction(transaction_2[2], { @@ -178,9 +176,7 @@ describe.each(['builtin', 'express'])('Remix API Actions with adapter = %s', ada }, }, }, - tags: { - transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - }, + transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, }); assertSentryEvent(event[2], { @@ -228,9 +224,7 @@ describe.each(['builtin', 'express'])('Remix API Actions with adapter = %s', ada }, }, }, - tags: { - transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - }, + transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, }); assertSentryEvent(event[2], { @@ -278,9 +272,7 @@ describe.each(['builtin', 'express'])('Remix API Actions with adapter = %s', ada }, }, }, - tags: { - transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - }, + transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, }); assertSentryEvent(event[2], { @@ -328,9 +320,7 @@ describe.each(['builtin', 'express'])('Remix API Actions with adapter = %s', ada }, }, }, - tags: { - transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - }, + transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, }); assertSentryEvent(event[2], { @@ -378,9 +368,7 @@ describe.each(['builtin', 'express'])('Remix API Actions with adapter = %s', ada }, }, }, - tags: { - transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, - }, + transaction: `routes/action-json-response${useV2 ? '.' : '/'}$id`, }); assertSentryEvent(event[2], { @@ -428,9 +416,7 @@ describe.each(['builtin', 'express'])('Remix API Actions with adapter = %s', ada }, }, }, - tags: { - transaction: `routes/server-side-unexpected-errors${useV2 ? '.' : '/'}$id`, - }, + transaction: `routes/server-side-unexpected-errors${useV2 ? '.' : '/'}$id`, }); assertSentryEvent(event[2], { @@ -478,9 +464,7 @@ describe.each(['builtin', 'express'])('Remix API Actions with adapter = %s', ada }, }, }, - tags: { - transaction: `routes/server-side-unexpected-errors${useV2 ? '.' : '/'}$id`, - }, + transaction: `routes/server-side-unexpected-errors${useV2 ? '.' : '/'}$id`, }); assertSentryEvent(event[2], { diff --git a/packages/remix/test/integration/test/server/loader.test.ts b/packages/remix/test/integration/test/server/loader.test.ts index aa25ca13cbb2..c40282096e18 100644 --- a/packages/remix/test/integration/test/server/loader.test.ts +++ b/packages/remix/test/integration/test/server/loader.test.ts @@ -126,9 +126,7 @@ describe.each(['builtin', 'express'])('Remix API Loaders with adapter = %s', ada }, }, }, - tags: { - transaction: `routes/loader-json-response${useV2 ? '.' : '/'}$id`, - }, + transaction: `routes/loader-json-response${useV2 ? '.' : '/'}$id`, }); assertSentryTransaction(transaction_2[2], { @@ -142,9 +140,7 @@ describe.each(['builtin', 'express'])('Remix API Loaders with adapter = %s', ada }, }, }, - tags: { - transaction: `routes/loader-json-response${useV2 ? '.' : '/'}$id`, - }, + transaction: `routes/loader-json-response${useV2 ? '.' : '/'}$id`, }); assertSentryEvent(event[2], { diff --git a/packages/replay/src/coreHandlers/handleAfterSendEvent.ts b/packages/replay/src/coreHandlers/handleAfterSendEvent.ts index 28e112e736f0..14e92d404fc7 100644 --- a/packages/replay/src/coreHandlers/handleAfterSendEvent.ts +++ b/packages/replay/src/coreHandlers/handleAfterSendEvent.ts @@ -1,20 +1,15 @@ -import { getClient } from '@sentry/core'; -import type { ErrorEvent, Event, TransactionEvent, Transport, TransportMakeRequestResponse } from '@sentry/types'; +import type { ErrorEvent, Event, TransactionEvent, TransportMakeRequestResponse } from '@sentry/types'; import type { ReplayContainer } from '../types'; import { isErrorEvent, isTransactionEvent } from '../util/eventUtils'; -type AfterSendEventCallback = (event: Event, sendResponse: TransportMakeRequestResponse | void) => void; +type AfterSendEventCallback = (event: Event, sendResponse: TransportMakeRequestResponse) => void; /** * Returns a listener to be added to `client.on('afterSendErrorEvent, listener)`. */ export function handleAfterSendEvent(replay: ReplayContainer): AfterSendEventCallback { - // Custom transports may still be returning `Promise`, which means we cannot expect the status code to be available there - // TODO (v8): remove this check as it will no longer be necessary - const enforceStatusCode = isBaseTransportSend(); - - return (event: Event, sendResponse: TransportMakeRequestResponse | void) => { + return (event: Event, sendResponse: TransportMakeRequestResponse) => { if (!replay.isEnabled() || (!isErrorEvent(event) && !isTransactionEvent(event))) { return; } @@ -24,7 +19,7 @@ export function handleAfterSendEvent(replay: ReplayContainer): AfterSendEventCal // We only want to do stuff on successful error sending, otherwise you get error replays without errors attached // If not using the base transport, we allow `undefined` response (as a custom transport may not implement this correctly yet) // If we do use the base transport, we skip if we encountered an non-OK status code - if (enforceStatusCode && (!statusCode || statusCode < 200 || statusCode >= 300)) { + if (!statusCode || statusCode < 200 || statusCode >= 300) { return; } @@ -79,19 +74,3 @@ function handleErrorEvent(replay: ReplayContainer, event: ErrorEvent): void { replay.sendBufferedReplayOrFlush(); }); } - -function isBaseTransportSend(): boolean { - const client = getClient(); - if (!client) { - return false; - } - - const transport = client.getTransport(); - if (!transport) { - return false; - } - - return ( - (transport.send as Transport['send'] & { __sentry__baseTransport__?: true }).__sentry__baseTransport__ || false - ); -} diff --git a/packages/replay/src/util/sendReplayRequest.ts b/packages/replay/src/util/sendReplayRequest.ts index c030ea1f8c2f..03945bb479af 100644 --- a/packages/replay/src/util/sendReplayRequest.ts +++ b/packages/replay/src/util/sendReplayRequest.ts @@ -1,6 +1,7 @@ import { getClient, getCurrentScope } from '@sentry/core'; import type { ReplayEvent, TransportMakeRequestResponse } from '@sentry/types'; import type { RateLimits } from '@sentry/utils'; +import { resolvedSyncPromise } from '@sentry/utils'; import { isRateLimited, updateRateLimits } from '@sentry/utils'; import { REPLAY_EVENT_NAME, UNABLE_TO_SEND_REPLAY } from '../constants'; @@ -20,7 +21,7 @@ export async function sendReplayRequest({ eventContext, timestamp, session, -}: SendReplayData): Promise { +}: SendReplayData): Promise { const preparedRecordingData = prepareRecordingData({ recordingData, headers: { @@ -36,7 +37,7 @@ export async function sendReplayRequest({ const dsn = client && client.getDsn(); if (!client || !transport || !dsn || !session.sampled) { - return; + return resolvedSyncPromise({}); } const baseEvent: ReplayEvent = { @@ -57,7 +58,7 @@ export async function sendReplayRequest({ // Taken from baseclient's `_processEvent` method, where this is handled for errors/transactions client.recordDroppedEvent('event_processor', 'replay', baseEvent); logInfo('An event processor returned `null`, will not send event.'); - return; + return resolvedSyncPromise({}); } /* @@ -102,7 +103,7 @@ export async function sendReplayRequest({ const envelope = createReplayEnvelope(replayEvent, preparedRecordingData, dsn, client.getOptions().tunnel); - let response: void | TransportMakeRequestResponse; + let response: TransportMakeRequestResponse; try { response = await transport.send(envelope); @@ -119,11 +120,6 @@ export async function sendReplayRequest({ throw error; } - // TODO (v8): we can remove this guard once transport.send's type signature doesn't include void anymore - if (!response) { - return response; - } - // If the status code is invalid, we want to immediately stop & not retry if (typeof response.statusCode === 'number' && (response.statusCode < 200 || response.statusCode >= 300)) { throw new TransportStatusCodeError(response.statusCode); diff --git a/packages/replay/test/integration/coreHandlers/handleAfterSendEvent.test.ts b/packages/replay/test/integration/coreHandlers/handleAfterSendEvent.test.ts index 3cb668a83a67..03f4c236b81d 100644 --- a/packages/replay/test/integration/coreHandlers/handleAfterSendEvent.test.ts +++ b/packages/replay/test/integration/coreHandlers/handleAfterSendEvent.test.ts @@ -35,8 +35,8 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { const handler = handleAfterSendEvent(replay); - // With undefined response: Don't capture - handler(error1, undefined); + // With empty response: Don't capture + handler(error1, {}); // With "successful" response: Capture handler(error2, { statusCode: 200 }); // With "unsuccessful" response: Don't capture @@ -66,8 +66,8 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { const handler = handleAfterSendEvent(replay); - // With undefined response: Don't capture - handler(transaction1, undefined); + // With empty response: Don't capture + handler(transaction1, {}); // With "successful" response: Capture handler(transaction2, { statusCode: 200 }); // With "unsuccessful" response: Don't capture @@ -141,40 +141,6 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { ); }); - it('allows undefined send response when using custom transport', async () => { - ({ replay } = await resetSdkMock({ - replayOptions: { - stickySession: false, - }, - sentryOptions: { - replaysSessionSampleRate: 1.0, - replaysOnErrorSampleRate: 0.0, - }, - })); - - const client = getClient()!; - // @ts-expect-error make sure to remove this - delete client.getTransport()!.send.__sentry__baseTransport__; - - const error1 = Error({ event_id: 'err1' }); - const error2 = Error({ event_id: 'err2' }); - const error3 = Error({ event_id: 'err3' }); - const error4 = Error({ event_id: 'err4' }); - - const handler = handleAfterSendEvent(replay); - - // With undefined response: Capture - handler(error1, undefined); - // With "successful" response: Capture - handler(error2, { statusCode: 200 }); - // With "unsuccessful" response: Capture - handler(error3, { statusCode: 0 }); - // With no statusCode response: Capture - handler(error4, { statusCode: undefined }); - - expect(Array.from(replay.getContext().errorIds)).toEqual(['err1', 'err2', 'err3', 'err4']); - }); - it('flushes when in buffer mode', async () => { ({ replay } = await resetSdkMock({ replayOptions: { @@ -302,7 +268,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { expect(replay.recordingMode).toBe('buffer'); - handler(error1, undefined); + handler(error1, {}); expect(Array.from(replay.getContext().errorIds)).toEqual([]); diff --git a/packages/replay/test/mocks/mockSdk.ts b/packages/replay/test/mocks/mockSdk.ts index ccf11dabb6ad..2ada7cec506f 100644 --- a/packages/replay/test/mocks/mockSdk.ts +++ b/packages/replay/test/mocks/mockSdk.ts @@ -16,16 +16,11 @@ class MockTransport implements Transport { send: (request: Envelope) => PromiseLike; constructor() { - const send: ((request: Envelope) => PromiseLike) & { - __sentry__baseTransport__?: boolean; - } = jest.fn(async () => { + this.send = jest.fn(async () => { return { statusCode: 200, }; }); - - send.__sentry__baseTransport__ = true; - this.send = send; } async flush() { diff --git a/packages/svelte/test/performance.test.ts b/packages/svelte/test/performance.test.ts index 8e7357da044e..dd8baea00637 100644 --- a/packages/svelte/test/performance.test.ts +++ b/packages/svelte/test/performance.test.ts @@ -1,13 +1,5 @@ import { act, render } from '@testing-library/svelte'; -import { - addTracingExtensions, - getClient, - getCurrentScope, - getIsolationScope, - init, - spanToJSON, - startSpan, -} from '../src'; +import { addTracingExtensions, getClient, getCurrentScope, getIsolationScope, init, startSpan } from '../src'; import type { TransactionEvent } from '@sentry/types'; import { vi } from 'vitest'; @@ -56,9 +48,9 @@ describe('Sentry.trackComponent()', () => { const rootSpanId = transaction.contexts?.trace?.span_id; expect(rootSpanId).toBeDefined(); - const initSpanId = transaction.spans![0].spanContext().spanId; + const initSpanId = transaction.spans![0].span_id; - expect(spanToJSON(transaction.spans![0])).toEqual({ + expect(transaction.spans![0]).toEqual({ data: { 'sentry.op': 'ui.svelte.init', 'sentry.origin': 'auto.ui.svelte', @@ -73,7 +65,7 @@ describe('Sentry.trackComponent()', () => { trace_id: expect.any(String), }); - expect(spanToJSON(transaction.spans![1])).toEqual({ + expect(transaction.spans![1]).toEqual({ data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte', @@ -111,9 +103,9 @@ describe('Sentry.trackComponent()', () => { const rootSpanId = transaction.contexts?.trace?.span_id; expect(rootSpanId).toBeDefined(); - const initSpanId = transaction.spans![0].spanContext().spanId; + const initSpanId = transaction.spans![0].span_id; - expect(spanToJSON(transaction.spans![0])).toEqual({ + expect(transaction.spans![0]).toEqual({ data: { 'sentry.op': 'ui.svelte.init', 'sentry.origin': 'auto.ui.svelte', @@ -128,7 +120,7 @@ describe('Sentry.trackComponent()', () => { trace_id: expect.any(String), }); - expect(spanToJSON(transaction.spans![1])).toEqual({ + expect(transaction.spans![1]).toEqual({ data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte', @@ -143,7 +135,7 @@ describe('Sentry.trackComponent()', () => { trace_id: expect.any(String), }); - expect(spanToJSON(transaction.spans![2])).toEqual({ + expect(transaction.spans![2]).toEqual({ data: { 'sentry.op': 'ui.svelte.update', 'sentry.origin': 'auto.ui.svelte', @@ -172,7 +164,7 @@ describe('Sentry.trackComponent()', () => { const transaction = transactions[0]; expect(transaction.spans).toHaveLength(1); - expect(spanToJSON(transaction.spans![0]).op).toEqual('ui.svelte.init'); + expect(transaction.spans![0].op).toEqual('ui.svelte.init'); }); it('only creates update spans if trackInit is deactivated', async () => { @@ -188,7 +180,7 @@ describe('Sentry.trackComponent()', () => { const transaction = transactions[0]; expect(transaction.spans).toHaveLength(1); - expect(spanToJSON(transaction.spans![0]).op).toEqual('ui.svelte.update'); + expect(transaction.spans![0].op).toEqual('ui.svelte.update'); }); it('creates no spans if trackInit and trackUpdates are deactivated', async () => { @@ -220,8 +212,8 @@ describe('Sentry.trackComponent()', () => { const transaction = transactions[0]; expect(transaction.spans).toHaveLength(2); - expect(spanToJSON(transaction.spans![0]).description).toEqual(''); - expect(spanToJSON(transaction.spans![1]).description).toEqual(''); + expect(transaction.spans![0].description).toEqual(''); + expect(transaction.spans![1].description).toEqual(''); }); it("doesn't do anything, if there's no ongoing transaction", async () => { @@ -253,7 +245,7 @@ describe('Sentry.trackComponent()', () => { // 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'); + expect(transaction.spans![0].op).toEqual('ui.svelte.init'); + expect(transaction.spans![1].op).toEqual('ui.svelte.update'); }); }); diff --git a/packages/sveltekit/src/vite/sourceMaps.ts b/packages/sveltekit/src/vite/sourceMaps.ts index 11f0946b06c8..96e288818aae 100644 --- a/packages/sveltekit/src/vite/sourceMaps.ts +++ b/packages/sveltekit/src/vite/sourceMaps.ts @@ -226,7 +226,7 @@ export async function makeCustomSentryVitePlugins(options?: CustomSentryVitePlug console.warn('[Source Maps Plugin] Failed to upload source maps!'); // eslint-disable-next-line no-console console.log( - '[Source Maps Plugin] Please make sure, you specified a valid Sentry auth token, as well as your org and project slugs.', + '[Source Maps Plugin] Please make sure you specified a valid Sentry auth token, as well as your org and project slugs.', ); // eslint-disable-next-line no-console console.log( diff --git a/packages/tracing-internal/src/browser/backgroundtab.ts b/packages/tracing-internal/src/browser/backgroundtab.ts index a221a7c09c82..ad0c57d55fe6 100644 --- a/packages/tracing-internal/src/browser/backgroundtab.ts +++ b/packages/tracing-internal/src/browser/backgroundtab.ts @@ -1,4 +1,4 @@ -import { getActiveSpan, getRootSpan } from '@sentry/core'; +import { SPAN_STATUS_ERROR, getActiveSpan, getRootSpan } from '@sentry/core'; import { spanToJSON } from '@sentry/core'; import { logger } from '@sentry/utils'; @@ -34,7 +34,7 @@ export function registerBackgroundTabDetection(): void { // We should not set status if it is already set, this prevent important statuses like // error or data loss from being overwritten on transaction. if (!status) { - rootSpan.setStatus(cancelledStatus); + rootSpan.setStatus({ code: SPAN_STATUS_ERROR, message: cancelledStatus }); } rootSpan.setAttribute('sentry.cancellation_reason', 'document.hidden'); diff --git a/packages/tracing-internal/src/browser/browserTracingIntegration.ts b/packages/tracing-internal/src/browser/browserTracingIntegration.ts index fa0b7d514358..9c0877045084 100644 --- a/packages/tracing-internal/src/browser/browserTracingIntegration.ts +++ b/packages/tracing-internal/src/browser/browserTracingIntegration.ts @@ -1,4 +1,5 @@ import type { IdleTransaction } from '@sentry/core'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN } from '@sentry/core'; import { getActiveSpan } from '@sentry/core'; import { getCurrentHub } from '@sentry/core'; import { @@ -232,7 +233,7 @@ export const browserTracingIntegration = ((_options: Partial next(params), ); diff --git a/packages/types/src/client.ts b/packages/types/src/client.ts index 1bd423116e8f..83b48ac24d9c 100644 --- a/packages/types/src/client.ts +++ b/packages/types/src/client.ts @@ -215,7 +215,7 @@ export interface Client { /** * Register a callback for when an event has been sent. */ - on(hook: 'afterSendEvent', callback: (event: Event, sendResponse: TransportMakeRequestResponse | void) => void): void; + on(hook: 'afterSendEvent', callback: (event: Event, sendResponse: TransportMakeRequestResponse) => void): void; /** * Register a callback before a breadcrumb is added. @@ -227,12 +227,6 @@ export interface Client { */ on(hook: 'createDsc', callback: (dsc: DynamicSamplingContext) => void): void; - /** - * Register a callback when an OpenTelemetry span is ended (in @sentry/opentelemetry-node). - * The option argument may be mutated to drop the span. - */ - on(hook: 'otelSpanEnd', callback: (otelSpan: unknown, mutableOptions: { drop: boolean }) => void): void; - /** * Register a callback when a Feedback event has been prepared. * This should be used to mutate the event. The options argument can hint @@ -298,7 +292,7 @@ export interface Client { * Fire a hook event after sending an event. Expects to be given an Event as the * second argument. */ - emit(hook: 'afterSendEvent', event: Event, sendResponse: TransportMakeRequestResponse | void): void; + emit(hook: 'afterSendEvent', event: Event, sendResponse: TransportMakeRequestResponse): void; /** * Fire a hook for when a breadcrumb is added. Expects the breadcrumb as second argument. @@ -310,13 +304,6 @@ export interface Client { */ emit(hook: 'createDsc', dsc: DynamicSamplingContext): void; - /** - * Fire a hook for when an OpenTelemetry span is ended (in @sentry/opentelemetry-node). - * Expects the OTEL span & as second argument, and an option object as third argument. - * The option argument may be mutated to drop the span. - */ - emit(hook: 'otelSpanEnd', otelSpan: unknown, mutableOptions: { drop: boolean }): void; - /** * Fire a hook event for after preparing a feedback event. Events to be given * a feedback event as the second argument, and an optional options object as diff --git a/packages/types/src/event.ts b/packages/types/src/event.ts index b64a9e4b1ef4..37146c674f52 100644 --- a/packages/types/src/event.ts +++ b/packages/types/src/event.ts @@ -11,7 +11,7 @@ import type { Request } from './request'; import type { CaptureContext } from './scope'; import type { SdkInfo } from './sdkinfo'; import type { SeverityLevel } from './severity'; -import type { MetricSummary, Span, SpanJSON } from './span'; +import type { MetricSummary, SpanJSON } from './span'; import type { Thread } from './thread'; import type { TransactionSource } from './transaction'; import type { User } from './user'; @@ -47,7 +47,7 @@ export interface Event { extra?: Extras; user?: User; type?: EventType; - spans?: Span[]; + spans?: Partial[]; measurements?: Measurements; debug_meta?: DebugMeta; // A place to stash data which is needed at some point in the SDK's event processing pipeline but which shouldn't get sent to Sentry @@ -86,14 +86,3 @@ export interface EventHint { data?: any; integrations?: string[]; } - -/** - * Represents the event that's sent in an event envelope, omitting interfaces that are no longer representative after - * event serialization. - */ -export interface SerializedEvent extends Omit { - /** - * POJO objects of spans belonging to this event. - */ - spans?: SpanJSON[]; -} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 57c565a86d6e..6a2216587a7f 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -47,7 +47,7 @@ export type { ProfileItem, } from './envelope'; export type { ExtendedError } from './error'; -export type { Event, EventHint, EventType, ErrorEvent, TransactionEvent, SerializedEvent } from './event'; +export type { Event, EventHint, EventType, ErrorEvent, TransactionEvent } from './event'; export type { EventProcessor } from './eventprocessor'; export type { Exception } from './exception'; export type { Extra, Extras } from './extra'; @@ -100,6 +100,7 @@ export type { TraceFlag, MetricSummary, } from './span'; +export type { SpanStatus } from './spanStatus'; export type { StackFrame } from './stackframe'; export type { Stacktrace, StackParser, StackLineParser, StackLineParserFn } from './stacktrace'; export type { PropagationContext, TracePropagationTargets } from './tracing'; diff --git a/packages/types/src/span.ts b/packages/types/src/span.ts index 018e84e977ec..ddc3057e6cbf 100644 --- a/packages/types/src/span.ts +++ b/packages/types/src/span.ts @@ -1,5 +1,6 @@ import type { Primitive } from './misc'; import type { HrTime } from './opentelemetry'; +import type { SpanStatus } from './spanStatus'; import type { TransactionSource } from './transaction'; type SpanOriginType = 'manual' | 'auto'; @@ -192,11 +193,9 @@ export interface Span { setAttributes(attributes: SpanAttributes): void; /** - * Sets the status attribute on the current span - * See: {@sentry/core SpanStatusType} for possible values - * @param status http code used to set the status + * Sets the status attribute on the current span. */ - setStatus(status: string): this; + setStatus(status: SpanStatus): this; /** * Update the name of the span. diff --git a/packages/types/src/spanStatus.ts b/packages/types/src/spanStatus.ts new file mode 100644 index 000000000000..8347d37adb1a --- /dev/null +++ b/packages/types/src/spanStatus.ts @@ -0,0 +1,60 @@ +type SpanStatusType = + /** The operation completed successfully. */ + | 'ok' + /** Deadline expired before operation could complete. */ + | 'deadline_exceeded' + /** 401 Unauthorized (actually does mean unauthenticated according to RFC 7235) */ + | 'unauthenticated' + /** 403 Forbidden */ + | 'permission_denied' + /** 404 Not Found. Some requested entity (file or directory) was not found. */ + | 'not_found' + /** 429 Too Many Requests */ + | 'resource_exhausted' + /** Client specified an invalid argument. 4xx. */ + | 'invalid_argument' + /** 501 Not Implemented */ + | 'unimplemented' + /** 503 Service Unavailable */ + | 'unavailable' + /** Other/generic 5xx. */ + | 'internal_error' + /** Unknown. Any non-standard HTTP status code. */ + | 'unknown_error' + /** The operation was cancelled (typically by the user). */ + | 'cancelled' + /** Already exists (409) */ + | 'already_exists' + /** Operation was rejected because the system is not in a state required for the operation's */ + | 'failed_precondition' + /** The operation was aborted, typically due to a concurrency issue. */ + | 'aborted' + /** Operation was attempted past the valid range. */ + | 'out_of_range' + /** Unrecoverable data loss or corruption */ + | 'data_loss'; + +// These are aligned with OpenTelemetry span status codes +const SPAN_STATUS_UNSET = 0; +const SPAN_STATUS_OK = 1; +const SPAN_STATUS_ERROR = 2; +/** The status code of a span. */ +export type SpanStatusCode = typeof SPAN_STATUS_UNSET | typeof SPAN_STATUS_OK | typeof SPAN_STATUS_ERROR; + +/** + * The status of a span. + * This can optionally contain a human-readable message. + */ +export interface SpanStatus { + /** + * The status code of this message. + * 0 = UNSET + * 1 = OK + * 2 = ERROR + */ + code: SpanStatusCode; + /** + * A developer-facing error message. + */ + message?: SpanStatusType | string; +} diff --git a/packages/types/src/startSpanOptions.ts b/packages/types/src/startSpanOptions.ts index 01293e129c74..d884d7edaeff 100644 --- a/packages/types/src/startSpanOptions.ts +++ b/packages/types/src/startSpanOptions.ts @@ -53,11 +53,6 @@ export interface StartSpanOptions extends TransactionContext { */ metadata?: Partial; - /** - * @deprecated Use `span.setStatus()` instead. - */ - status?: string; - /** * @deprecated Use `scope` instead. */ diff --git a/packages/types/src/transport.ts b/packages/types/src/transport.ts index 6866f8d19032..07186b141420 100644 --- a/packages/types/src/transport.ts +++ b/packages/types/src/transport.ts @@ -27,8 +27,7 @@ export interface BaseTransportOptions extends InternalBaseTransportOptions { } export interface Transport { - // TODO (v8) Remove void from return as it was only retained to avoid a breaking change - send(request: Envelope): PromiseLike; + send(request: Envelope): PromiseLike; flush(timeout?: number): PromiseLike; } diff --git a/packages/utils/src/vendor/supportsHistory.ts b/packages/utils/src/vendor/supportsHistory.ts index 8dc16b43f1cc..35af156eb96f 100644 --- a/packages/utils/src/vendor/supportsHistory.ts +++ b/packages/utils/src/vendor/supportsHistory.ts @@ -38,8 +38,8 @@ export function supportsHistory(): boolean { // borrowed from: https://github.com/angular/angular.js/pull/13945/files /* eslint-disable @typescript-eslint/no-unsafe-member-access */ // eslint-disable-next-line @typescript-eslint/no-explicit-any - const chrome = (WINDOW as any).chrome; - const isChromePackagedApp = chrome && chrome.app && chrome.app.runtime; + const chromeVar = (WINDOW as any).chrome; + const isChromePackagedApp = chromeVar && chromeVar.app && chromeVar.app.runtime; /* eslint-enable @typescript-eslint/no-unsafe-member-access */ const hasHistoryApi = 'history' in WINDOW && !!WINDOW.history.pushState && !!WINDOW.history.replaceState; diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index f09c61bdac90..b37ec1b92470 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -78,7 +78,6 @@ export { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, } from '@sentry/core'; -export type { SpanStatusType } from '@sentry/core'; export { VercelEdgeClient } from './client'; export { @@ -86,15 +85,13 @@ export { init, } from './sdk'; -import { Integrations as CoreIntegrations, RequestData } from '@sentry/core'; +import { RequestData } from '@sentry/core'; import { WinterCGFetch } from './integrations/wintercg-fetch'; export { winterCGFetchIntegration } from './integrations/wintercg-fetch'; /** @deprecated Import the integration function directly, e.g. `inboundFiltersIntegration()` instead of `new Integrations.InboundFilter(). */ export const Integrations = { - // eslint-disable-next-line deprecation/deprecation - ...CoreIntegrations, WinterCGFetch, RequestData, }; diff --git a/packages/vercel-edge/src/transports/index.ts b/packages/vercel-edge/src/transports/index.ts index b2b899ecdb42..4e8a35ac7c39 100644 --- a/packages/vercel-edge/src/transports/index.ts +++ b/packages/vercel-edge/src/transports/index.ts @@ -37,13 +37,13 @@ export class IsolatedPromiseBuffer { /** * @inheritdoc */ - public add(taskProducer: () => PromiseLike): PromiseLike { + public add(taskProducer: () => PromiseLike): PromiseLike { if (this._taskProducers.length >= this._bufferSize) { return Promise.reject(new SentryError('Not adding Promise because buffer limit was reached.')); } this._taskProducers.push(taskProducer); - return Promise.resolve(); + return Promise.resolve({}); } /** diff --git a/packages/vercel-edge/test/wintercg-fetch.test.ts b/packages/vercel-edge/test/wintercg-fetch.test.ts index f9e1c3ca8aed..6439795870f7 100644 --- a/packages/vercel-edge/test/wintercg-fetch.test.ts +++ b/packages/vercel-edge/test/wintercg-fetch.test.ts @@ -29,7 +29,7 @@ describe('WinterCGFetch instrumentation', () => { tracesSampleRate: 1, integrations: [], transport: () => ({ - send: () => Promise.resolve(undefined), + send: () => Promise.resolve({}), flush: () => Promise.resolve(true), }), tracePropagationTargets: ['http://my-website.com/'], diff --git a/packages/vue/src/tracing.ts b/packages/vue/src/tracing.ts index 277acc959c3f..70f662559adf 100644 --- a/packages/vue/src/tracing.ts +++ b/packages/vue/src/tracing.ts @@ -1,4 +1,4 @@ -import { getActiveSpan, startInactiveSpan } from '@sentry/browser'; +import { SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN, getActiveSpan, startInactiveSpan } from '@sentry/browser'; import type { Span } from '@sentry/types'; import { logger, timestampInSeconds } from '@sentry/utils'; @@ -75,7 +75,9 @@ export const createTracingMixins = (options: TracingOptions): Mixins => { startInactiveSpan({ name: 'Application Render', op: `${VUE_OP}.render`, - origin: 'auto.ui.vue', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.vue', + }, }); } } @@ -109,7 +111,9 @@ export const createTracingMixins = (options: TracingOptions): Mixins => { this.$_sentrySpans[operation] = startInactiveSpan({ name: `Vue <${name}>`, op: `${VUE_OP}.${operation}`, - origin: 'auto.ui.vue', + attributes: { + [SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.ui.vue', + }, }); } } else { diff --git a/scripts/node-unit-tests.ts b/scripts/node-unit-tests.ts index c876552d2d8d..972c6d76db65 100644 --- a/scripts/node-unit-tests.ts +++ b/scripts/node-unit-tests.ts @@ -19,6 +19,7 @@ const DEFAULT_SKIP_TESTS_PACKAGES = [ '@sentry/profiling-node', '@sentry/replay', '@sentry-internal/replay-canvas', + '@sentry-internal/replay-worker', '@sentry-internal/feedback', '@sentry/wasm', '@sentry/bun', diff --git a/yarn.lock b/yarn.lock index 2fe21ddf431a..1268c1052158 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5771,40 +5771,40 @@ magic-string "0.27.0" unplugin "1.0.1" -"@sentry/cli-darwin@2.28.6": - version "2.28.6" - resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.28.6.tgz#83f9127de77e2a2d25eb143d90720b3e9042adc1" - integrity sha512-KRf0VvTltHQ5gA7CdbUkaIp222LAk/f1+KqpDzO6nB/jC/tL4sfiy6YyM4uiH6IbVEudB8WpHCECiatmyAqMBA== - -"@sentry/cli-linux-arm64@2.28.6": - version "2.28.6" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.28.6.tgz#6bb660e5d8145270e287a9a21201d2f9576b0634" - integrity sha512-caMDt37FI752n4/3pVltDjlrRlPFCOxK4PHvoZGQ3KFMsai0ZhE/0CLBUMQqfZf0M0r8KB2x7wqLm7xSELjefQ== - -"@sentry/cli-linux-arm@2.28.6": - version "2.28.6" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.28.6.tgz#73d466004ac445d9258e83a7b3d4e0ee6604e0bd" - integrity sha512-ANG7U47yEHD1g3JrfhpT4/MclEvmDZhctWgSP5gVw5X4AlcI87E6dTqccnLgvZjiIAQTaJJAZuSHVVF3Jk403w== - -"@sentry/cli-linux-i686@2.28.6": - version "2.28.6" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.28.6.tgz#f7175ca639ee05cf12d808f7fc31d59d6e2ee3b9" - integrity sha512-Tj1+GMc6lFsDRquOqaGKXFpW9QbmNK4TSfynkWKiJxdTEn5jSMlXXfr0r9OQrxu3dCCqEHkhEyU63NYVpgxIPw== - -"@sentry/cli-linux-x64@2.28.6": - version "2.28.6" - resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.28.6.tgz#df0af8d6c8c8c880eb7345c715a4dfa509544a40" - integrity sha512-Dt/Xz784w/z3tEObfyJEMmRIzn0D5qoK53H9kZ6e0yNvJOSKNCSOq5cQk4n1/qeG0K/6SU9dirmvHwFUiVNyYg== - -"@sentry/cli-win32-i686@2.28.6": - version "2.28.6" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.28.6.tgz#0df19912d1823b6ec034b4c4c714c7601211c926" - integrity sha512-zkpWtvY3kt+ogVaAbfFr2MEkgMMHJNJUnNMO8Ixce9gh38sybIkDkZNFnVPBXMClJV0APa4QH0EwumYBFZUMuQ== - -"@sentry/cli-win32-x64@2.28.6": - version "2.28.6" - resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.28.6.tgz#2344a206be3b555ec6540740f93a181199962804" - integrity sha512-TG2YzZ9JMeNFzbicdr5fbtsusVGACbrEfHmPgzWGDeLUP90mZxiMTjkXsE1X/5jQEQjB2+fyfXloba/Ugo51hA== +"@sentry/cli-darwin@2.29.1": + version "2.29.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.29.1.tgz#09f84f2af156bac446733117b48f5d743d90467f" + integrity sha512-FfYkME4stq6q1sWWsxFAjTa1pxc+GV+HTHYcnajqU+X0rQReoOvFvi00AHua1qKy8+lH5/RgEvMJcDBdoa0h3A== + +"@sentry/cli-linux-arm64@2.29.1": + version "2.29.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.29.1.tgz#5ecc844aa50178f7a6ab31eb28deea0fea7f5119" + integrity sha512-xcwNMJDC10kbi7T1kXRtZCMfgBtD3qLRgYEHJR0UuTONF+pF80uacdde+7oABW+rA3jy2G+4+cR5pN9M2x8wGQ== + +"@sentry/cli-linux-arm@2.29.1": + version "2.29.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.29.1.tgz#54d418fff574faf6bd23b57d5a6df62f637e36d6" + integrity sha512-lqOsmAVVgRYZzNyGQA1jnmALx0LnZ/Z7zc8/2TFrQYT7FUps5FP3yDDKlsUTaB8Z8A0++k7AqFgwlIeJw7+mPQ== + +"@sentry/cli-linux-i686@2.29.1": + version "2.29.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.29.1.tgz#d4131f36effc51b12b2f1bbef6c03b8361fac877" + integrity sha512-yOe7YEx42vl0Q8MgkbQmLbRe2KjijjMP+l9DYAfbOJHdTJ+3tNfBcRqFXsVNiobGxUeaVE5oDkOJ+7iGGgM2HA== + +"@sentry/cli-linux-x64@2.29.1": + version "2.29.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.29.1.tgz#922277cacba03fc7d8634b0efc3257c99a622e11" + integrity sha512-fzKh/lRwzZSb+QIebuWN9qXXABGarsL9V2xONGwWv202AueK0WFxr6/ADsE7OlmM/4cDby5Sd+oMKos2eeVcVA== + +"@sentry/cli-win32-i686@2.29.1": + version "2.29.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.29.1.tgz#93bc1b88832b498537da94a7cf8f3a688ce635b2" + integrity sha512-qdzg2nCSLfMgwhH7HLb6nPngJgbDvcOfDPtF+Y7axoe18iiQW0Ju7M9/e30t8vaOcUz9ZowQgFvxdgYlHJ3jMw== + +"@sentry/cli-win32-x64@2.29.1": + version "2.29.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.29.1.tgz#1bc0a746f3a94f7ed21108095e9cb5c9f585b7ad" + integrity sha512-CtGuRmwJ06WVMyVOq/Oa0l/1fI7rSAxcAlCzVAvj1p09aLXAGObGXmljXUeQ1jxE/Ll4yRAo9TjA8Y+vxPtkJw== "@sentry/cli@^1.74.4", "@sentry/cli@^1.77.1": version "1.77.1" @@ -5818,10 +5818,10 @@ proxy-from-env "^1.1.0" which "^2.0.2" -"@sentry/cli@^2.22.3", "@sentry/cli@^2.28.6": - version "2.28.6" - resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.28.6.tgz#645f31b9e742e7bf7668c8f867149359e79b8123" - integrity sha512-o2Ngz7xXuhwHxMi+4BFgZ4qjkX0tdZeOSIZkFAGnTbRhQe5T8bxq6CcQRLdPhqMgqvDn7XuJ3YlFtD3ZjHvD7g== +"@sentry/cli@^2.22.3", "@sentry/cli@^2.29.1": + version "2.29.1" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.29.1.tgz#1089aabbdd2b5c34263f1de7e89bbed5b7dc06ef" + integrity sha512-STtuGm8vdR4ifR3I6mFmt9y7Dk9QzeOvpyXEG5H7dkFDSvU7cepkYAlli5QiZDzECngxGx/uLAzZzFKSATF1Qw== dependencies: https-proxy-agent "^5.0.0" node-fetch "^2.6.7" @@ -5829,13 +5829,13 @@ proxy-from-env "^1.1.0" which "^2.0.2" optionalDependencies: - "@sentry/cli-darwin" "2.28.6" - "@sentry/cli-linux-arm" "2.28.6" - "@sentry/cli-linux-arm64" "2.28.6" - "@sentry/cli-linux-i686" "2.28.6" - "@sentry/cli-linux-x64" "2.28.6" - "@sentry/cli-win32-i686" "2.28.6" - "@sentry/cli-win32-x64" "2.28.6" + "@sentry/cli-darwin" "2.29.1" + "@sentry/cli-linux-arm" "2.29.1" + "@sentry/cli-linux-arm64" "2.29.1" + "@sentry/cli-linux-i686" "2.29.1" + "@sentry/cli-linux-x64" "2.29.1" + "@sentry/cli-win32-i686" "2.29.1" + "@sentry/cli-win32-x64" "2.29.1" "@sentry/vite-plugin@2.14.2", "@sentry/vite-plugin@^2.14.2": version "2.14.2"