diff --git a/MIGRATION.md b/MIGRATION.md index 99e9e3023732..d55a287fac99 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -8,6 +8,17 @@ npx @sentry/migr8@latest This will let you select which updates to run, and automatically update your code. Make sure to still review all code changes! +## Deprecate `startTransaction()` + +In v8, the old performance API `startTransaction()` (as well as `hub.startTransaction()`) will be removed. +Instead, use the new performance APIs: + +* `startSpan()` +* `startSpanManual()` +* `startInactiveSpan()` + +You can [read more about the new performance APIs here](./docs/v8-new-performance-apis.md). + ## Deprecate `Sentry.lastEventId()` and `hub.lastEventId()` `Sentry.lastEventId()` sometimes causes race conditons, so we are deprecating it in favour of the `beforeSend` callback. diff --git a/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/success.ts b/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/success.ts index 8a5a53f2e4dc..4b7b8703332a 100644 --- a/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/success.ts +++ b/dev-packages/e2e-tests/test-applications/create-next-app/pages/api/success.ts @@ -3,6 +3,7 @@ import * as Sentry from '@sentry/nextjs'; import type { NextApiRequest, NextApiResponse } from 'next'; export default function handler(req: NextApiRequest, res: NextApiResponse) { + // eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ name: 'test-transaction', op: 'e2e-test' }); Sentry.getCurrentHub().getScope().setSpan(transaction); diff --git a/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts b/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts index 269f8df45bbe..8ab1935c723e 100644 --- a/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts +++ b/dev-packages/e2e-tests/test-applications/node-express-app/src/app.ts @@ -34,6 +34,7 @@ app.get('/test-param/:param', function (req, res) { }); app.get('/test-transaction', async function (req, res) { + // eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ name: 'test-transaction', op: 'e2e-test' }); Sentry.getCurrentScope().setSpan(transaction); diff --git a/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/scenario.ts index 1e4931a2bae7..70596da19716 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/scenario.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startTransaction/basic-usage/scenario.ts @@ -9,6 +9,7 @@ Sentry.init({ tracesSampleRate: 1.0, }); +// eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ name: 'test_transaction_1' }); transaction.end(); diff --git a/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/scenario.ts b/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/scenario.ts index a340de7b21fe..97fc6874770f 100644 --- a/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/scenario.ts +++ b/dev-packages/node-integration-tests/suites/public-api/startTransaction/with-nested-spans/scenario.ts @@ -8,6 +8,7 @@ Sentry.init({ tracesSampleRate: 1.0, }); +// eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ name: 'test_transaction_1' }); const span_1 = transaction.startChild({ op: 'span_1', diff --git a/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts index b37bd6df6fc9..6e9ea3a5f097 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/apollo-graphql/scenario.ts @@ -27,6 +27,7 @@ const server = new ApolloServer({ resolvers, }); +// eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ name: 'test_transaction', op: 'transaction' }); Sentry.getCurrentScope().setSpan(transaction); diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts index 36f8c3503832..63949c64d531 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mongodb/scenario.ts @@ -16,6 +16,7 @@ const client = new MongoClient(process.env.MONGO_URL || '', { }); async function run(): Promise { + // eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ name: 'Test Transaction', op: 'transaction', diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withConnect/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withConnect/scenario.ts index 9b033e72a669..a1b2343e4a61 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withConnect/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withConnect/scenario.ts @@ -19,6 +19,7 @@ connection.connect(function (err: unknown) { } }); +// eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ op: 'transaction', name: 'Test Transaction', diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/scenario.ts index 23d07f346875..aa6d8ca9ade3 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutCallback/scenario.ts @@ -19,6 +19,7 @@ connection.connect(function (err: unknown) { } }); +// eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ op: 'transaction', name: 'Test Transaction', diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutConnect/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutConnect/scenario.ts index bf9e4bf90e35..9a4b2dc4141a 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutConnect/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/mysql/withoutConnect/scenario.ts @@ -13,6 +13,7 @@ const connection = mysql.createConnection({ password: 'docker', }); +// eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ op: 'transaction', name: 'Test Transaction', diff --git a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts index b41be87c9550..ad90c387ed20 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/auto-instrument/pg/scenario.ts @@ -8,6 +8,7 @@ Sentry.init({ integrations: [...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations()], }); +// eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ op: 'transaction', name: 'Test Transaction', diff --git a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts index 20847871e7a1..26c3b81f6e44 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/prisma-orm/scenario.ts @@ -13,6 +13,7 @@ Sentry.init({ }); async function run(): Promise { + // eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ name: 'Test Transaction', op: 'transaction', diff --git a/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts b/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts index 7c86686cbba8..2a8a34dae4f9 100644 --- a/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing-new/tracePropagationTargets/scenario.ts @@ -10,6 +10,7 @@ Sentry.init({ integrations: [new Sentry.Integrations.Http({ tracing: true })], }); +// eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ name: 'test_transaction' }); Sentry.getCurrentScope().setSpan(transaction); diff --git a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts index 7b34ffab0613..aac11a641032 100644 --- a/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/apollo-graphql/scenario.ts @@ -29,6 +29,7 @@ const server = new ApolloServer({ resolvers, }); +// eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ name: 'test_transaction', op: 'transaction' }); Sentry.getCurrentScope().setSpan(transaction); diff --git a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/scenario.ts index cff8329d22a3..f11e7f39d923 100644 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mongodb/scenario.ts @@ -17,6 +17,7 @@ const client = new MongoClient(process.env.MONGO_URL || '', { }); async function run(): Promise { + // eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ name: 'Test Transaction', op: 'transaction', diff --git a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mysql/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mysql/scenario.ts index 30f9fb368b3a..de77c5fa7c8a 100644 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mysql/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/mysql/scenario.ts @@ -20,6 +20,7 @@ connection.connect(function (err: unknown) { } }); +// eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ op: 'transaction', name: 'Test Transaction', diff --git a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/scenario.ts index 95248c82f075..1963c61fc31b 100644 --- a/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/auto-instrument/pg/scenario.ts @@ -9,6 +9,7 @@ Sentry.init({ tracesSampleRate: 1.0, }); +// eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ op: 'transaction', name: 'Test Transaction', diff --git a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts index 7e8a7c6eca5f..087d5ce860f2 100644 --- a/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/prisma-orm/scenario.ts @@ -15,6 +15,7 @@ Sentry.init({ }); async function run(): Promise { + // eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ name: 'Test Transaction', op: 'transaction', diff --git a/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts b/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts index 9fdeba1fcb95..f1e395992b46 100644 --- a/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts +++ b/dev-packages/node-integration-tests/suites/tracing/tracePropagationTargets/scenario.ts @@ -12,6 +12,7 @@ Sentry.init({ integrations: [new Sentry.Integrations.Http({ tracing: true })], }); +// eslint-disable-next-line deprecation/deprecation const transaction = Sentry.startTransaction({ name: 'test_transaction' }); Sentry.getCurrentScope().setSpan(transaction); diff --git a/packages/astro/src/index.server.ts b/packages/astro/src/index.server.ts index 6a1011053007..b038e215496d 100644 --- a/packages/astro/src/index.server.ts +++ b/packages/astro/src/index.server.ts @@ -32,6 +32,7 @@ export { Hub, makeMain, Scope, + // eslint-disable-next-line deprecation/deprecation startTransaction, SDK_VERSION, setContext, diff --git a/packages/browser/src/exports.ts b/packages/browser/src/exports.ts index 0d87e7898283..f63fe20fdead 100644 --- a/packages/browser/src/exports.ts +++ b/packages/browser/src/exports.ts @@ -45,6 +45,7 @@ export { lastEventId, makeMain, Scope, + // eslint-disable-next-line deprecation/deprecation startTransaction, getActiveSpan, startSpan, diff --git a/packages/bun/src/index.ts b/packages/bun/src/index.ts index 964e34560bdf..749299badd74 100644 --- a/packages/bun/src/index.ts +++ b/packages/bun/src/index.ts @@ -52,6 +52,7 @@ export { makeMain, runWithAsyncContext, Scope, + // eslint-disable-next-line deprecation/deprecation startTransaction, SDK_VERSION, setContext, diff --git a/packages/core/src/exports.ts b/packages/core/src/exports.ts index d479a3093d51..09989215ef3e 100644 --- a/packages/core/src/exports.ts +++ b/packages/core/src/exports.ts @@ -190,11 +190,14 @@ export function withScope(callback: (scope: Scope) => T): T { * default values). See {@link Options.tracesSampler}. * * @returns The transaction which was just started + * + * @deprecated Use `startSpan()`, `startSpanManual()` or `startInactiveSpan()` instead. */ export function startTransaction( context: TransactionContext, customSamplingContext?: CustomSamplingContext, ): ReturnType { + // eslint-disable-next-line deprecation/deprecation return getCurrentHub().startTransaction({ ...context }, customSamplingContext); } diff --git a/packages/core/src/hub.ts b/packages/core/src/hub.ts index 07f1310f94a2..d05b80410d06 100644 --- a/packages/core/src/hub.ts +++ b/packages/core/src/hub.ts @@ -440,7 +440,23 @@ export class Hub implements HubInterface { } /** - * @inheritDoc + * Starts a new `Transaction` and returns it. This is the entry point to manual tracing instrumentation. + * + * A tree structure can be built by adding child spans to the transaction, and child spans to other spans. To start a + * new child span within the transaction or any span, call the respective `.startChild()` method. + * + * Every child span must be finished before the transaction is finished, otherwise the unfinished spans are discarded. + * + * The transaction must be finished with a call to its `.end()` method, at which point the transaction with all its + * finished child spans will be sent to Sentry. + * + * @param context Properties of the new `Transaction`. + * @param customSamplingContext Information given to the transaction sampling function (along with context-dependent + * default values). See {@link Options.tracesSampler}. + * + * @returns The transaction which was just started + * + * @deprecated Use `startSpan()`, `startSpanManual()` or `startInactiveSpan()` instead. */ public startTransaction(context: TransactionContext, customSamplingContext?: CustomSamplingContext): Transaction { const result = this._callExtensionMethod('startTransaction', context, customSamplingContext); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 20748ff11589..2c423f0744c3 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -19,6 +19,7 @@ export { flush, // eslint-disable-next-line deprecation/deprecation lastEventId, + // eslint-disable-next-line deprecation/deprecation startTransaction, setContext, setExtra, diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index b5d49f4767a7..347417bcb460 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -156,7 +156,10 @@ export function startInactiveSpan(context: TransactionContext): Span | undefined const hub = getCurrentHub(); const parentSpan = getActiveSpan(); - return parentSpan ? parentSpan.startChild(ctx) : hub.startTransaction(ctx); + return parentSpan + ? parentSpan.startChild(ctx) + : // eslint-disable-next-line deprecation/deprecation + hub.startTransaction(ctx); } /** @@ -235,7 +238,10 @@ function createChildSpanOrTransaction( if (!hasTracingEnabled()) { return undefined; } - return parentSpan ? parentSpan.startChild(ctx) : hub.startTransaction(ctx); + return parentSpan + ? parentSpan.startChild(ctx) + : // eslint-disable-next-line deprecation/deprecation + hub.startTransaction(ctx); } function normalizeContext(context: TransactionContext): TransactionContext { diff --git a/packages/core/test/lib/tracing/errors.test.ts b/packages/core/test/lib/tracing/errors.test.ts index 60b5db5c0c1d..d2714901d39e 100644 --- a/packages/core/test/lib/tracing/errors.test.ts +++ b/packages/core/test/lib/tracing/errors.test.ts @@ -1,5 +1,5 @@ import { BrowserClient } from '@sentry/browser'; -import { Hub, addTracingExtensions, makeMain } from '@sentry/core'; +import { Hub, addTracingExtensions, makeMain, startInactiveSpan, startSpan } from '@sentry/core'; import type { HandlerDataError, HandlerDataUnhandledRejection } from '@sentry/types'; import { getDefaultBrowserClientOptions } from '../../../../tracing/test/testutils'; @@ -30,19 +30,14 @@ beforeAll(() => { }); describe('registerErrorHandlers()', () => { - let hub: Hub; beforeEach(() => { mockAddGlobalErrorInstrumentationHandler.mockClear(); mockAddGlobalUnhandledRejectionInstrumentationHandler.mockClear(); - const options = getDefaultBrowserClientOptions(); - hub = new Hub(new BrowserClient(options)); + const options = getDefaultBrowserClientOptions({ enableTracing: true }); + const hub = new Hub(new BrowserClient(options)); makeMain(hub); }); - afterEach(() => { - hub.getScope().setSpan(undefined); - }); - it('registers error instrumentation', () => { registerErrorInstrumentation(); expect(mockAddGlobalErrorInstrumentationHandler).toHaveBeenCalledTimes(1); @@ -53,7 +48,8 @@ describe('registerErrorHandlers()', () => { it('does not set status if transaction is not on scope', () => { registerErrorInstrumentation(); - const transaction = hub.startTransaction({ name: 'test' }); + + const transaction = startInactiveSpan({ name: 'test' })!; expect(transaction.status).toBe(undefined); mockErrorCallback({} as HandlerDataError); @@ -66,22 +62,19 @@ describe('registerErrorHandlers()', () => { it('sets status for transaction on scope on error', () => { registerErrorInstrumentation(); - const transaction = hub.startTransaction({ name: 'test' }); - hub.getScope().setSpan(transaction); - - mockErrorCallback({} as HandlerDataError); - expect(transaction.status).toBe('internal_error'); - transaction.end(); + startSpan({ name: 'test' }, span => { + mockErrorCallback({} as HandlerDataError); + expect(span?.status).toBe('internal_error'); + }); }); it('sets status for transaction on scope on unhandledrejection', () => { registerErrorInstrumentation(); - const transaction = hub.startTransaction({ name: 'test' }); - hub.getScope().setSpan(transaction); - mockUnhandledRejectionCallback({}); - expect(transaction.status).toBe('internal_error'); - transaction.end(); + startSpan({ name: 'test' }, span => { + mockUnhandledRejectionCallback({}); + expect(span?.status).toBe('internal_error'); + }); }); }); diff --git a/packages/deno/src/index.ts b/packages/deno/src/index.ts index 2530e2f7bdc5..f70d511ae8a3 100644 --- a/packages/deno/src/index.ts +++ b/packages/deno/src/index.ts @@ -51,6 +51,7 @@ export { makeMain, runWithAsyncContext, Scope, + // eslint-disable-next-line deprecation/deprecation startTransaction, SDK_VERSION, setContext, diff --git a/packages/hub/src/index.ts b/packages/hub/src/index.ts index 057d0e6a9975..579b5b932e7e 100644 --- a/packages/hub/src/index.ts +++ b/packages/hub/src/index.ts @@ -118,6 +118,7 @@ export const configureScope = configureScopeCore; /** * @deprecated This export has moved to @sentry/core. The @sentry/hub package will be removed in v8. */ +// eslint-disable-next-line deprecation/deprecation export const startTransaction = startTransactionCore; /** diff --git a/packages/nextjs/src/common/utils/wrapperUtils.ts b/packages/nextjs/src/common/utils/wrapperUtils.ts index e25220ce61c2..da0c11df8640 100644 --- a/packages/nextjs/src/common/utils/wrapperUtils.ts +++ b/packages/nextjs/src/common/utils/wrapperUtils.ts @@ -100,6 +100,8 @@ export function withTracedServerSideDataFetcher Pr if (platformSupportsStreaming()) { let spanToContinue: Span; if (previousSpan === undefined) { + // TODO: Refactor this to use `startSpan()` + // eslint-disable-next-line deprecation/deprecation const newTransaction = startTransaction( { op: 'http.server', @@ -136,6 +138,8 @@ export function withTracedServerSideDataFetcher Pr status: 'ok', }); } else { + // TODO: Refactor this to use `startSpan()` + // eslint-disable-next-line deprecation/deprecation dataFetcherSpan = startTransaction({ op: 'function.nextjs', name: `${options.dataFetchingMethodName} (${options.dataFetcherRouteName})`, diff --git a/packages/nextjs/test/clientSdk.test.ts b/packages/nextjs/test/clientSdk.test.ts index 1b35f82cbfe8..fd43f1bee9ad 100644 --- a/packages/nextjs/test/clientSdk.test.ts +++ b/packages/nextjs/test/clientSdk.test.ts @@ -1,6 +1,6 @@ import { BaseClient, getCurrentHub } from '@sentry/core'; import * as SentryReact from '@sentry/react'; -import { BrowserTracing, WINDOW } from '@sentry/react'; +import { BrowserTracing, WINDOW, getCurrentScope } from '@sentry/react'; import type { Integration } from '@sentry/types'; import type { UserIntegrationsFunction } from '@sentry/utils'; import { logger } from '@sentry/utils'; @@ -89,8 +89,12 @@ describe('Client init()', () => { const hub = getCurrentHub(); const transportSend = jest.spyOn(hub.getClient()!.getTransport()!, 'send'); - const transaction = hub.startTransaction({ name: '/404' }); - transaction.end(); + // Ensure we have no current span, so our next span is a transaction + getCurrentScope().setSpan(undefined); + + SentryReact.startSpan({ name: '/404' }, () => { + // noop + }); expect(transportSend).not.toHaveBeenCalled(); expect(captureEvent.mock.results[0].value).toBeUndefined(); diff --git a/packages/nextjs/test/serverSdk.test.ts b/packages/nextjs/test/serverSdk.test.ts index 0813d4931874..6201ddf34a53 100644 --- a/packages/nextjs/test/serverSdk.test.ts +++ b/packages/nextjs/test/serverSdk.test.ts @@ -105,8 +105,9 @@ describe('Server init()', () => { const hub = getCurrentHub(); const transportSend = jest.spyOn(hub.getClient()!.getTransport()!, 'send'); - const transaction = hub.startTransaction({ name: '/404' }); - transaction.end(); + SentryNode.startSpan({ name: '/404' }, () => { + // noop + }); // We need to flush because the event processor pipeline is async whereas transaction.end() is sync. await SentryNode.flush(); diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index 9a4bb08bfb4b..6d4c6f5a4494 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -63,6 +63,8 @@ export function tracingHandler(): ( const [name, source] = extractPathForTransaction(req, { path: true, method: true }); const transaction = continueTrace({ sentryTrace, baggage }, ctx => + // TODO: Refactor this to use `startSpan()` + // eslint-disable-next-line deprecation/deprecation startTransaction( { name, diff --git a/packages/node/src/index.ts b/packages/node/src/index.ts index e712a4fc0d0d..47206462b937 100644 --- a/packages/node/src/index.ts +++ b/packages/node/src/index.ts @@ -51,6 +51,7 @@ export { makeMain, runWithAsyncContext, Scope, + // eslint-disable-next-line deprecation/deprecation startTransaction, SDK_VERSION, setContext, diff --git a/packages/node/src/integrations/hapi/index.ts b/packages/node/src/integrations/hapi/index.ts index 732839d3995c..409470680a73 100644 --- a/packages/node/src/integrations/hapi/index.ts +++ b/packages/node/src/integrations/hapi/index.ts @@ -75,6 +75,7 @@ export const hapiTracingPlugin = { baggage: request.headers['baggage'] || undefined, }, transactionContext => { + // eslint-disable-next-line deprecation/deprecation return startTransaction({ ...transactionContext, op: 'hapi.request', diff --git a/packages/node/test/integrations/http.test.ts b/packages/node/test/integrations/http.test.ts index 42eb9391ec52..91d5a6c0e20d 100644 --- a/packages/node/test/integrations/http.test.ts +++ b/packages/node/test/integrations/http.test.ts @@ -1,6 +1,8 @@ import * as http from 'http'; import * as https from 'https'; -import type { Span, Transaction } from '@sentry/core'; +import type { Span } from '@sentry/core'; +import { Transaction } from '@sentry/core'; +import { startInactiveSpan } from '@sentry/core'; import * as sentryCore from '@sentry/core'; import { Hub, addTracingExtensions } from '@sentry/core'; import type { TransactionContext } from '@sentry/types'; @@ -36,6 +38,7 @@ describe('tracing', () => { ...customOptions, }); const hub = new Hub(new NodeClient(options)); + sentryCore.makeMain(hub); addTracingExtensions(); hub.getScope().setUser({ @@ -47,12 +50,14 @@ describe('tracing', () => { jest.spyOn(sentryCore, 'getCurrentScope').mockImplementation(() => hub.getScope()); jest.spyOn(sentryCore, 'getClient').mockReturnValue(hub.getClient()); - const transaction = hub.startTransaction({ + const transaction = startInactiveSpan({ name: 'dogpark', traceId: '12312012123120121231201212312012', ...customContext, }); + expect(transaction).toBeInstanceOf(Transaction); + hub.getScope().setSpan(transaction); return transaction; @@ -367,7 +372,7 @@ describe('tracing', () => { function createTransactionAndPutOnScope(hub: Hub) { addTracingExtensions(); - const transaction = hub.startTransaction({ name: 'dogpark' }); + const transaction = startInactiveSpan({ name: 'dogpark' }); hub.getScope().setSpan(transaction); return transaction; } diff --git a/packages/node/test/integrations/undici.test.ts b/packages/node/test/integrations/undici.test.ts index 078d90c98721..1d7f847b2503 100644 --- a/packages/node/test/integrations/undici.test.ts +++ b/packages/node/test/integrations/undici.test.ts @@ -1,5 +1,5 @@ import * as http from 'http'; -import type { Transaction } from '@sentry/core'; +import { Transaction, startSpan } from '@sentry/core'; import { spanToTraceHeader } from '@sentry/core'; import { Hub, makeMain, runWithAsyncContext } from '@sentry/core'; import type { fetch as FetchType } from 'undici'; @@ -106,65 +106,73 @@ conditionalTest({ min: 16 })('Undici integration', () => { }, ], ])('creates a span with a %s', async (_: string, request, requestInit, expected) => { - const transaction = hub.startTransaction({ name: 'test-transaction' }) as Transaction; - hub.getScope().setSpan(transaction); + await startSpan({ name: 'outer-span' }, async outerSpan => { + await fetch(request, requestInit); - await fetch(request, requestInit); + expect(outerSpan).toBeInstanceOf(Transaction); + const spans = (outerSpan as Transaction).spanRecorder?.spans || []; - expect(transaction.spanRecorder?.spans.length).toBe(2); + expect(spans.length).toBe(2); - const span = transaction.spanRecorder?.spans[1]; - expect(span).toEqual(expect.objectContaining(expected)); + const span = spans[1]; + expect(span).toEqual(expect.objectContaining(expected)); + }); }); it('creates a span with internal errors', async () => { - const transaction = hub.startTransaction({ name: 'test-transaction' }) as Transaction; - hub.getScope().setSpan(transaction); + await startSpan({ name: 'outer-span' }, async outerSpan => { + try { + await fetch('http://a-url-that-no-exists.com'); + } catch (e) { + // ignore + } - try { - await fetch('http://a-url-that-no-exists.com'); - } catch (e) { - // ignore - } + expect(outerSpan).toBeInstanceOf(Transaction); + const spans = (outerSpan as Transaction).spanRecorder?.spans || []; - expect(transaction.spanRecorder?.spans.length).toBe(2); + expect(spans.length).toBe(2); - const span = transaction.spanRecorder?.spans[1]; - expect(span).toEqual(expect.objectContaining({ status: 'internal_error' })); + const span = spans[1]; + expect(span).toEqual(expect.objectContaining({ status: 'internal_error' })); + }); }); it('creates a span for invalid looking urls', async () => { - const transaction = hub.startTransaction({ name: 'test-transaction' }) as Transaction; - hub.getScope().setSpan(transaction); - - try { - // Intentionally add // to the url - // fetch accepts this URL, but throws an error later on - await fetch('http://a-url-that-no-exists.com//'); - } catch (e) { - // ignore - } - - expect(transaction.spanRecorder?.spans.length).toBe(2); - - const span = transaction.spanRecorder?.spans[1]; - expect(span).toEqual(expect.objectContaining({ description: 'GET http://a-url-that-no-exists.com//' })); - expect(span).toEqual(expect.objectContaining({ status: 'internal_error' })); + await startSpan({ name: 'outer-span' }, async outerSpan => { + try { + // Intentionally add // to the url + // fetch accepts this URL, but throws an error later on + await fetch('http://a-url-that-no-exists.com//'); + } catch (e) { + // ignore + } + + expect(outerSpan).toBeInstanceOf(Transaction); + const spans = (outerSpan as Transaction).spanRecorder?.spans || []; + + expect(spans.length).toBe(2); + + const span = spans[1]; + expect(span).toEqual(expect.objectContaining({ description: 'GET http://a-url-that-no-exists.com//' })); + expect(span).toEqual(expect.objectContaining({ status: 'internal_error' })); + }); }); it('does not create a span for sentry requests', async () => { - const transaction = hub.startTransaction({ name: 'test-transaction' }) as Transaction; - hub.getScope().setSpan(transaction); + await startSpan({ name: 'outer-span' }, async outerSpan => { + try { + await fetch(`${SENTRY_DSN}/sub/route`, { + method: 'POST', + }); + } catch (e) { + // ignore + } - try { - await fetch(`${SENTRY_DSN}/sub/route`, { - method: 'POST', - }); - } catch (e) { - // ignore - } + expect(outerSpan).toBeInstanceOf(Transaction); + const spans = (outerSpan as Transaction).spanRecorder?.spans || []; - expect(transaction.spanRecorder?.spans.length).toBe(1); + expect(spans.length).toBe(1); + }); }); it('does not create a span if there is no active spans', async () => { @@ -178,20 +186,22 @@ conditionalTest({ min: 16 })('Undici integration', () => { }); it('does create a span if `shouldCreateSpanForRequest` is defined', async () => { - const transaction = hub.startTransaction({ name: 'test-transaction' }) as Transaction; - hub.getScope().setSpan(transaction); + await startSpan({ name: 'outer-span' }, async outerSpan => { + expect(outerSpan).toBeInstanceOf(Transaction); + const spans = (outerSpan as Transaction).spanRecorder?.spans || []; - const undoPatch = patchUndici({ shouldCreateSpanForRequest: url => url.includes('yes') }); + const undoPatch = patchUndici({ shouldCreateSpanForRequest: url => url.includes('yes') }); - await fetch('http://localhost:18100/no', { method: 'POST' }); + await fetch('http://localhost:18100/no', { method: 'POST' }); - expect(transaction.spanRecorder?.spans.length).toBe(1); + expect(spans.length).toBe(1); - await fetch('http://localhost:18100/yes', { method: 'POST' }); + await fetch('http://localhost:18100/yes', { method: 'POST' }); - expect(transaction.spanRecorder?.spans.length).toBe(2); + expect(spans.length).toBe(2); - undoPatch(); + undoPatch(); + }); }); // This flakes on CI for some reason: https://github.com/getsentry/sentry-javascript/pull/8449 @@ -200,18 +210,20 @@ conditionalTest({ min: 16 })('Undici integration', () => { expect.assertions(3); await runWithAsyncContext(async () => { - const transaction = hub.startTransaction({ name: 'test-transaction' }) as Transaction; - hub.getScope().setSpan(transaction); + await startSpan({ name: 'outer-span' }, async outerSpan => { + expect(outerSpan).toBeInstanceOf(Transaction); + const spans = (outerSpan as Transaction).spanRecorder?.spans || []; - await fetch('http://localhost:18100', { method: 'POST' }); + await fetch('http://localhost:18100', { method: 'POST' }); - expect(transaction.spanRecorder?.spans.length).toBe(2); - const span = transaction.spanRecorder?.spans[1]; + expect(spans.length).toBe(2); + const span = spans[1]; - expect(requestHeaders['sentry-trace']).toEqual(spanToTraceHeader(span!)); - expect(requestHeaders['baggage']).toEqual( - `sentry-environment=production,sentry-public_key=0,sentry-trace_id=${transaction.traceId},sentry-sample_rate=1,sentry-transaction=test-transaction`, - ); + expect(requestHeaders['sentry-trace']).toEqual(spanToTraceHeader(span!)); + expect(requestHeaders['baggage']).toEqual( + `sentry-environment=production,sentry-public_key=0,sentry-trace_id=${span.traceId},sentry-sample_rate=1,sentry-transaction=test-transaction`, + ); + }); }); }); @@ -233,59 +245,62 @@ conditionalTest({ min: 16 })('Undici integration', () => { // This flakes on CI for some reason: https://github.com/getsentry/sentry-javascript/pull/8449 // eslint-disable-next-line jest/no-disabled-tests it.skip('attaches headers if `shouldCreateSpanForRequest` does not create a span using propagation context', async () => { - const transaction = hub.startTransaction({ name: 'test-transaction' }) as Transaction; const scope = hub.getScope(); const propagationContext = scope.getPropagationContext(); - scope.setSpan(transaction); + await startSpan({ name: 'outer-span' }, async outerSpan => { + expect(outerSpan).toBeInstanceOf(Transaction); - const undoPatch = patchUndici({ shouldCreateSpanForRequest: url => url.includes('yes') }); + const undoPatch = patchUndici({ shouldCreateSpanForRequest: url => url.includes('yes') }); - await fetch('http://localhost:18100/no', { method: 'POST' }); + await fetch('http://localhost:18100/no', { method: 'POST' }); - expect(requestHeaders['sentry-trace']).toBeDefined(); - expect(requestHeaders['baggage']).toBeDefined(); + expect(requestHeaders['sentry-trace']).toBeDefined(); + expect(requestHeaders['baggage']).toBeDefined(); - expect(requestHeaders['sentry-trace'].includes(propagationContext.traceId)).toBe(true); - const firstSpanId = requestHeaders['sentry-trace'].split('-')[1]; + expect(requestHeaders['sentry-trace'].includes(propagationContext.traceId)).toBe(true); + const firstSpanId = requestHeaders['sentry-trace'].split('-')[1]; - await fetch('http://localhost:18100/yes', { method: 'POST' }); + await fetch('http://localhost:18100/yes', { method: 'POST' }); - expect(requestHeaders['sentry-trace']).toBeDefined(); - expect(requestHeaders['baggage']).toBeDefined(); + expect(requestHeaders['sentry-trace']).toBeDefined(); + expect(requestHeaders['baggage']).toBeDefined(); - expect(requestHeaders['sentry-trace'].includes(propagationContext.traceId)).toBe(false); + expect(requestHeaders['sentry-trace'].includes(propagationContext.traceId)).toBe(false); - const secondSpanId = requestHeaders['sentry-trace'].split('-')[1]; - expect(firstSpanId).not.toBe(secondSpanId); + const secondSpanId = requestHeaders['sentry-trace'].split('-')[1]; + expect(firstSpanId).not.toBe(secondSpanId); - undoPatch(); + undoPatch(); + }); }); // This flakes on CI for some reason: https://github.com/getsentry/sentry-javascript/pull/8449 // eslint-disable-next-line jest/no-disabled-tests it.skip('uses tracePropagationTargets', async () => { - const transaction = hub.startTransaction({ name: 'test-transaction' }) as Transaction; - hub.getScope().setSpan(transaction); - const client = new NodeClient({ ...DEFAULT_OPTIONS, tracePropagationTargets: ['/yes'] }); hub.bindClient(client); - expect(transaction.spanRecorder?.spans.length).toBe(1); + await startSpan({ name: 'outer-span' }, async outerSpan => { + expect(outerSpan).toBeInstanceOf(Transaction); + const spans = (outerSpan as Transaction).spanRecorder?.spans || []; + + expect(spans.length).toBe(1); - await fetch('http://localhost:18100/no', { method: 'POST' }); + await fetch('http://localhost:18100/no', { method: 'POST' }); - expect(transaction.spanRecorder?.spans.length).toBe(2); + expect(spans.length).toBe(2); - expect(requestHeaders['sentry-trace']).toBeUndefined(); - expect(requestHeaders['baggage']).toBeUndefined(); + expect(requestHeaders['sentry-trace']).toBeUndefined(); + expect(requestHeaders['baggage']).toBeUndefined(); - await fetch('http://localhost:18100/yes', { method: 'POST' }); + await fetch('http://localhost:18100/yes', { method: 'POST' }); - expect(transaction.spanRecorder?.spans.length).toBe(3); + expect(spans.length).toBe(3); - expect(requestHeaders['sentry-trace']).toBeDefined(); - expect(requestHeaders['baggage']).toBeDefined(); + expect(requestHeaders['sentry-trace']).toBeDefined(); + expect(requestHeaders['baggage']).toBeDefined(); + }); }); it('adds a breadcrumb on request', async () => { diff --git a/packages/opentelemetry-node/src/spanprocessor.ts b/packages/opentelemetry-node/src/spanprocessor.ts index bb2372a3c2b4..5b1a1684c9cf 100644 --- a/packages/opentelemetry-node/src/spanprocessor.ts +++ b/packages/opentelemetry-node/src/spanprocessor.ts @@ -66,6 +66,7 @@ export class SentrySpanProcessor implements OtelSpanProcessor { setSentrySpan(otelSpanId, sentryChildSpan); } else { const traceCtx = getTraceData(otelSpan, parentContext); + // eslint-disable-next-line deprecation/deprecation const transaction = getCurrentHub().startTransaction({ name: otelSpan.name, ...traceCtx, diff --git a/packages/opentelemetry/test/custom/hubextensions.test.ts b/packages/opentelemetry/test/custom/hubextensions.test.ts index 44b7b941161d..6e246763a92a 100644 --- a/packages/opentelemetry/test/custom/hubextensions.test.ts +++ b/packages/opentelemetry/test/custom/hubextensions.test.ts @@ -14,6 +14,7 @@ describe('hubextensions', () => { const mockConsole = jest.spyOn(console, 'warn').mockImplementation(() => {}); + // eslint-disable-next-line deprecation/deprecation const transaction = getCurrentHub().startTransaction({ name: 'test' }); expect(transaction).toEqual({}); diff --git a/packages/remix/src/index.server.ts b/packages/remix/src/index.server.ts index 9bbe5f03641e..b28ce95caa61 100644 --- a/packages/remix/src/index.server.ts +++ b/packages/remix/src/index.server.ts @@ -31,6 +31,7 @@ export { Hub, makeMain, Scope, + // eslint-disable-next-line deprecation/deprecation startTransaction, SDK_VERSION, setContext, diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index f557542e64ce..e07710dc340a 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -394,6 +394,8 @@ export function startRequestHandlerTransaction( ); hub.getScope().setPropagationContext(propagationContext); + // TODO: Refactor this to `startSpan()` + // eslint-disable-next-line deprecation/deprecation const transaction = hub.startTransaction({ name, op: 'http.server', diff --git a/packages/serverless/src/index.ts b/packages/serverless/src/index.ts index 488ffec7a1ec..ce076283b635 100644 --- a/packages/serverless/src/index.ts +++ b/packages/serverless/src/index.ts @@ -41,6 +41,7 @@ export { setTag, setTags, setUser, + // eslint-disable-next-line deprecation/deprecation startTransaction, withScope, NodeClient, diff --git a/packages/sveltekit/src/server/index.ts b/packages/sveltekit/src/server/index.ts index 2086b3515551..61419c196736 100644 --- a/packages/sveltekit/src/server/index.ts +++ b/packages/sveltekit/src/server/index.ts @@ -29,6 +29,7 @@ export { Hub, makeMain, Scope, + // eslint-disable-next-line deprecation/deprecation startTransaction, SDK_VERSION, setContext, diff --git a/packages/sveltekit/test/server/handle.test.ts b/packages/sveltekit/test/server/handle.test.ts index cca809006d27..9f974a6bbdd1 100644 --- a/packages/sveltekit/test/server/handle.test.ts +++ b/packages/sveltekit/test/server/handle.test.ts @@ -358,34 +358,34 @@ describe('addSentryCodeToPage', () => { it('adds meta tags and the fetch proxy script if there is an active transaction', () => { const transformPageChunk = addSentryCodeToPage({}); - const transaction = hub.startTransaction({ name: 'test' }); - hub.getScope().setSpan(transaction); - const transformed = transformPageChunk({ html, done: true }) as string; + SentryNode.startSpan({ name: 'test' }, () => { + const transformed = transformPageChunk({ html, done: true }) as string; - expect(transformed).toContain('${FETCH_PROXY_SCRIPT}`); + expect(transformed).toContain('${FETCH_PROXY_SCRIPT}`); + }); }); it('adds a nonce attribute to the script if the `fetchProxyScriptNonce` option is specified', () => { const transformPageChunk = addSentryCodeToPage({ fetchProxyScriptNonce: '123abc' }); - const transaction = hub.startTransaction({ name: 'test' }); - hub.getScope().setSpan(transaction); - const transformed = transformPageChunk({ html, done: true }) as string; + SentryNode.startSpan({ name: 'test' }, () => { + const transformed = transformPageChunk({ html, done: true }) as string; - expect(transformed).toContain('${FETCH_PROXY_SCRIPT}`); + expect(transformed).toContain('${FETCH_PROXY_SCRIPT}`); + }); }); it('does not add the fetch proxy script if the `injectFetchProxyScript` option is false', () => { const transformPageChunk = addSentryCodeToPage({ injectFetchProxyScript: false }); - const transaction = hub.startTransaction({ name: 'test' }); - hub.getScope().setSpan(transaction); - const transformed = transformPageChunk({ html, done: true }) as string; + SentryNode.startSpan({ name: 'test' }, () => { + const transformed = transformPageChunk({ html, done: true }) as string; - expect(transformed).toContain('${FETCH_PROXY_SCRIPT}`); + expect(transformed).toContain('${FETCH_PROXY_SCRIPT}`); + }); }); }); diff --git a/packages/tracing-internal/test/browser/backgroundtab.test.ts b/packages/tracing-internal/test/browser/backgroundtab.test.ts index 2687d59069c5..704f3ba89b1c 100644 --- a/packages/tracing-internal/test/browser/backgroundtab.test.ts +++ b/packages/tracing-internal/test/browser/backgroundtab.test.ts @@ -1,4 +1,4 @@ -import { Hub, makeMain } from '@sentry/core'; +import { Hub, makeMain, startSpan } from '@sentry/core'; import { JSDOM } from 'jsdom'; import { addExtensionMethods } from '../../../tracing/src'; @@ -47,16 +47,15 @@ conditionalTest({ min: 10 })('registerBackgroundTabDetection', () => { it('finishes a transaction on visibility change', () => { registerBackgroundTabDetection(); - const transaction = hub.startTransaction({ name: 'test' }); - hub.getScope().setSpan(transaction); - - // Simulate document visibility hidden event - // @ts-expect-error need to override global document - global.document.hidden = true; - events.visibilitychange(); - - expect(transaction.status).toBe('cancelled'); - expect(transaction.tags.visibilitychange).toBe('document.hidden'); - expect(transaction.endTimestamp).toBeDefined(); + startSpan({ name: 'test' }, span => { + // Simulate document visibility hidden event + // @ts-expect-error need to override global document + global.document.hidden = true; + events.visibilitychange(); + + expect(span?.status).toBe('cancelled'); + expect(span?.tags.visibilitychange).toBe('document.hidden'); + expect(span?.endTimestamp).toBeDefined(); + }); }); }); diff --git a/packages/tracing/test/index.test.ts b/packages/tracing/test/index.test.ts index 79a61cd66dd6..e30bb92c0e5d 100644 --- a/packages/tracing/test/index.test.ts +++ b/packages/tracing/test/index.test.ts @@ -5,6 +5,7 @@ import { BrowserTracing, Integrations } from '../src'; describe('index', () => { it('patches the global hub to add an implementation for `Hub.startTransaction` as a side effect', () => { const hub = getCurrentHub(); + // eslint-disable-next-line deprecation/deprecation const transaction = hub.startTransaction({ name: 'test', endTimestamp: 123 }); expect(transaction).toBeDefined(); }); diff --git a/packages/types/src/hub.ts b/packages/types/src/hub.ts index f5ec2ea4fbb2..f15d17e3013d 100644 --- a/packages/types/src/hub.ts +++ b/packages/types/src/hub.ts @@ -214,6 +214,8 @@ export interface Hub { * default values). See {@link Options.tracesSampler}. * * @returns The transaction which was just started + * + * @deprecated Use `startSpan()`, `startSpanManual()` or `startInactiveSpan()` instead. */ startTransaction(context: TransactionContext, customSamplingContext?: CustomSamplingContext): Transaction; diff --git a/packages/vercel-edge/src/index.ts b/packages/vercel-edge/src/index.ts index 0dd3e722c6bf..ff8d97fbf398 100644 --- a/packages/vercel-edge/src/index.ts +++ b/packages/vercel-edge/src/index.ts @@ -51,6 +51,7 @@ export { makeMain, runWithAsyncContext, Scope, + // eslint-disable-next-line deprecation/deprecation startTransaction, SDK_VERSION, setContext,