diff --git a/CHANGELOG.md b/CHANGELOG.md index 56c5223e65e9..06d7d4b58e3c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ - "You miss 100 percent of the chances you don't take. — Wayne Gretzky" — Michael Scott +This release adds a new `enableTracing` option, which can be used instead of `tracesSampleRate` for an easier setup. +Related to this, the `hasTracingEnabled` utility function was moved from `@sentry/tracing` to `@sentry/core`. +The old export from `@sentry/tracing` has been deprecated and will be removed in v8. + ## 7.37.2 This release includes changes and fixes around text masking and blocking in Replay's `rrweb` dependency. See versions [1.102.0](https://github.com/getsentry/rrweb/releases/tag/1.102.0) and [1.103.0](https://github.com/getsentry/rrweb/releases/tag/1.103.0). diff --git a/package.json b/package.json index 606308f1dbe2..bd6bd97c3970 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "lint:eslint": "lerna run lint:eslint", "postpublish": "lerna run --stream --concurrency 1 postpublish", "test": "lerna run --ignore @sentry-internal/* test", + "test:unit": "lerna run --ignore @sentry-internal/* test:unit", "test-ci-browser": "lerna run test --ignore \"@sentry/{node,opentelemetry-node,serverless,nextjs,remix,gatsby}\" --ignore @sentry-internal/*", "test-ci-node": "ts-node ./scripts/node-unit-tests.ts", "test:update-snapshots": "lerna run test:update-snapshots" diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index e63591f4bc99..715a18a9a719 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -30,6 +30,7 @@ export { SDK_VERSION } from './version'; export { getIntegrationsToSetup } from './integration'; export { FunctionToString, InboundFilters } from './integrations'; export { prepareEvent } from './utils/prepareEvent'; +export { hasTracingEnabled } from './utils/hasTracingEnabled'; import * as Integrations from './integrations'; diff --git a/packages/core/src/utils/hasTracingEnabled.ts b/packages/core/src/utils/hasTracingEnabled.ts new file mode 100644 index 000000000000..65dad5883ba2 --- /dev/null +++ b/packages/core/src/utils/hasTracingEnabled.ts @@ -0,0 +1,23 @@ +import type { Options } from '@sentry/types'; + +import { getCurrentHub } from '../hub'; + +// Treeshakable guard to remove all code related to tracing +declare const __SENTRY_TRACING__: boolean | undefined; + +/** + * Determines if tracing is currently enabled. + * + * Tracing is enabled when at least one of `tracesSampleRate` and `tracesSampler` is defined in the SDK config. + */ +export function hasTracingEnabled( + maybeOptions?: Pick | undefined, +): boolean { + if (typeof __SENTRY_TRACING__ === 'boolean' && !__SENTRY_TRACING__) { + return false; + } + + const client = getCurrentHub().getClient(); + const options = maybeOptions || (client && client.getOptions()); + return !!options && (options.enableTracing || 'tracesSampleRate' in options || 'tracesSampler' in options); +} diff --git a/packages/core/test/lib/utils/hasTracingEnabled.test.ts b/packages/core/test/lib/utils/hasTracingEnabled.test.ts new file mode 100644 index 000000000000..a03ff25c9be9 --- /dev/null +++ b/packages/core/test/lib/utils/hasTracingEnabled.test.ts @@ -0,0 +1,32 @@ +import { hasTracingEnabled } from '../../../src'; + +describe('hasTracingEnabled', () => { + const tracesSampler = () => 1; + const tracesSampleRate = 1; + it.each([ + ['No options', undefined, false], + ['No tracesSampler or tracesSampleRate or enableTracing', {}, false], + ['With tracesSampler', { tracesSampler }, true], + ['With tracesSampleRate', { tracesSampleRate }, true], + ['With enableTracing=true', { enableTracing: true }, true], + ['With enableTracing=false', { enableTracing: false }, false], + ['With tracesSampler && enableTracing=false', { tracesSampler, enableTracing: false }, true], + ['With tracesSampleRate && enableTracing=false', { tracesSampler, enableTracing: false }, true], + ['With tracesSampler and tracesSampleRate', { tracesSampler, tracesSampleRate }, true], + [ + 'With tracesSampler and tracesSampleRate and enableTracing=true', + { tracesSampler, tracesSampleRate, enableTracing: true }, + true, + ], + [ + 'With tracesSampler and tracesSampleRate and enableTracing=false', + { tracesSampler, tracesSampleRate, enableTracing: false }, + true, + ], + ])( + '%s', + (_: string, input: Parameters[0], output: ReturnType) => { + expect(hasTracingEnabled(input)).toBe(output); + }, + ); +}); diff --git a/packages/gatsby/package.json b/packages/gatsby/package.json index 13d39c8b74de..159c5a9ca231 100644 --- a/packages/gatsby/package.json +++ b/packages/gatsby/package.json @@ -20,6 +20,7 @@ "access": "public" }, "dependencies": { + "@sentry/core": "7.38.0", "@sentry/react": "7.38.0", "@sentry/tracing": "7.38.0", "@sentry/types": "7.38.0", diff --git a/packages/gatsby/src/utils/integrations.ts b/packages/gatsby/src/utils/integrations.ts index 96cabb33bb68..680ef61765cc 100644 --- a/packages/gatsby/src/utils/integrations.ts +++ b/packages/gatsby/src/utils/integrations.ts @@ -1,3 +1,4 @@ +import { hasTracingEnabled } from '@sentry/core'; import * as Tracing from '@sentry/tracing'; import type { Integration } from '@sentry/types'; @@ -13,7 +14,7 @@ export type UserIntegrations = Integration[] | UserFnIntegrations; * @param options The options users have defined. */ export function getIntegrationsFromOptions(options: GatsbyOptions): UserIntegrations { - const isTracingEnabled = Tracing.hasTracingEnabled(options); + const isTracingEnabled = hasTracingEnabled(options); if (options.integrations === undefined) { return getIntegrationsFromArray([], isTracingEnabled); } else if (Array.isArray(options.integrations)) { diff --git a/packages/nextjs/src/client/index.ts b/packages/nextjs/src/client/index.ts index 27eb57858e40..302d0c90cfc8 100644 --- a/packages/nextjs/src/client/index.ts +++ b/packages/nextjs/src/client/index.ts @@ -1,7 +1,8 @@ +import { hasTracingEnabled } from '@sentry/core'; import { RewriteFrames } from '@sentry/integrations'; import type { BrowserOptions } from '@sentry/react'; import { configureScope, init as reactInit, Integrations } from '@sentry/react'; -import { BrowserTracing, defaultRequestInstrumentationOptions, hasTracingEnabled } from '@sentry/tracing'; +import { BrowserTracing, defaultRequestInstrumentationOptions } from '@sentry/tracing'; import type { EventProcessor } from '@sentry/types'; import { getVercelEnv } from '../common/getVercelEnv'; diff --git a/packages/nextjs/src/edge/utils/edgeWrapperUtils.ts b/packages/nextjs/src/edge/utils/edgeWrapperUtils.ts index b02605584554..384074e317bd 100644 --- a/packages/nextjs/src/edge/utils/edgeWrapperUtils.ts +++ b/packages/nextjs/src/edge/utils/edgeWrapperUtils.ts @@ -1,5 +1,4 @@ -import { captureException, getCurrentHub, startTransaction } from '@sentry/core'; -import { hasTracingEnabled } from '@sentry/tracing'; +import { captureException, getCurrentHub, hasTracingEnabled, startTransaction } from '@sentry/core'; import type { Span } from '@sentry/types'; import { addExceptionMechanism, diff --git a/packages/nextjs/src/server/index.ts b/packages/nextjs/src/server/index.ts index b27d07d11733..348eff6778d9 100644 --- a/packages/nextjs/src/server/index.ts +++ b/packages/nextjs/src/server/index.ts @@ -1,9 +1,8 @@ import type { Carrier } from '@sentry/core'; -import { getHubFromCarrier, getMainCarrier } from '@sentry/core'; +import { getHubFromCarrier, getMainCarrier, hasTracingEnabled } from '@sentry/core'; import { RewriteFrames } from '@sentry/integrations'; import type { NodeOptions } from '@sentry/node'; import { configureScope, getCurrentHub, init as nodeInit, Integrations } from '@sentry/node'; -import { hasTracingEnabled } from '@sentry/tracing'; import type { EventProcessor } from '@sentry/types'; import { escapeStringForRegex, logger } from '@sentry/utils'; import * as domainModule from 'domain'; diff --git a/packages/nextjs/src/server/wrapApiHandlerWithSentry.ts b/packages/nextjs/src/server/wrapApiHandlerWithSentry.ts index 6ba3d1851acf..5bed5dca3135 100644 --- a/packages/nextjs/src/server/wrapApiHandlerWithSentry.ts +++ b/packages/nextjs/src/server/wrapApiHandlerWithSentry.ts @@ -1,5 +1,6 @@ +import { hasTracingEnabled } from '@sentry/core'; import { captureException, getCurrentHub, startTransaction } from '@sentry/node'; -import { extractTraceparentData, hasTracingEnabled } from '@sentry/tracing'; +import { extractTraceparentData } from '@sentry/tracing'; import type { Transaction } from '@sentry/types'; import { addExceptionMechanism, diff --git a/packages/nextjs/src/server/wrapAppGetInitialPropsWithSentry.ts b/packages/nextjs/src/server/wrapAppGetInitialPropsWithSentry.ts index 953f75142b32..4499b6bacee2 100644 --- a/packages/nextjs/src/server/wrapAppGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/server/wrapAppGetInitialPropsWithSentry.ts @@ -1,5 +1,5 @@ +import { hasTracingEnabled } from '@sentry/core'; import { getCurrentHub } from '@sentry/node'; -import { hasTracingEnabled } from '@sentry/tracing'; import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; import type App from 'next/app'; diff --git a/packages/nextjs/src/server/wrapDocumentGetInitialPropsWithSentry.ts b/packages/nextjs/src/server/wrapDocumentGetInitialPropsWithSentry.ts index a3d1aa46eaea..e7d0d6eac621 100644 --- a/packages/nextjs/src/server/wrapDocumentGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/server/wrapDocumentGetInitialPropsWithSentry.ts @@ -1,5 +1,5 @@ +import { hasTracingEnabled } from '@sentry/core'; import { getCurrentHub } from '@sentry/node'; -import { hasTracingEnabled } from '@sentry/tracing'; import type Document from 'next/document'; import { isBuild } from './utils/isBuild'; diff --git a/packages/nextjs/src/server/wrapErrorGetInitialPropsWithSentry.ts b/packages/nextjs/src/server/wrapErrorGetInitialPropsWithSentry.ts index bf01ec8e4e84..176474f0e0c8 100644 --- a/packages/nextjs/src/server/wrapErrorGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/server/wrapErrorGetInitialPropsWithSentry.ts @@ -1,5 +1,5 @@ +import { hasTracingEnabled } from '@sentry/core'; import { getCurrentHub } from '@sentry/node'; -import { hasTracingEnabled } from '@sentry/tracing'; import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; import type { NextPageContext } from 'next'; import type { ErrorProps } from 'next/error'; diff --git a/packages/nextjs/src/server/wrapGetInitialPropsWithSentry.ts b/packages/nextjs/src/server/wrapGetInitialPropsWithSentry.ts index c180dfe3b9be..466a1438468b 100644 --- a/packages/nextjs/src/server/wrapGetInitialPropsWithSentry.ts +++ b/packages/nextjs/src/server/wrapGetInitialPropsWithSentry.ts @@ -1,5 +1,5 @@ +import { hasTracingEnabled } from '@sentry/core'; import { getCurrentHub } from '@sentry/node'; -import { hasTracingEnabled } from '@sentry/tracing'; import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; import type { NextPage } from 'next'; diff --git a/packages/nextjs/src/server/wrapGetServerSidePropsWithSentry.ts b/packages/nextjs/src/server/wrapGetServerSidePropsWithSentry.ts index e305a72686d5..21e58e3f4eb3 100644 --- a/packages/nextjs/src/server/wrapGetServerSidePropsWithSentry.ts +++ b/packages/nextjs/src/server/wrapGetServerSidePropsWithSentry.ts @@ -1,5 +1,5 @@ +import { hasTracingEnabled } from '@sentry/core'; import { getCurrentHub } from '@sentry/node'; -import { hasTracingEnabled } from '@sentry/tracing'; import { dynamicSamplingContextToSentryBaggageHeader } from '@sentry/utils'; import type { GetServerSideProps } from 'next'; diff --git a/packages/nextjs/src/server/wrapGetStaticPropsWithSentry.ts b/packages/nextjs/src/server/wrapGetStaticPropsWithSentry.ts index e21978580ea6..d8c7cc8f68ab 100644 --- a/packages/nextjs/src/server/wrapGetStaticPropsWithSentry.ts +++ b/packages/nextjs/src/server/wrapGetStaticPropsWithSentry.ts @@ -1,5 +1,5 @@ +import { hasTracingEnabled } from '@sentry/core'; import { getCurrentHub } from '@sentry/node'; -import { hasTracingEnabled } from '@sentry/tracing'; import type { GetStaticProps } from 'next'; import { isBuild } from './utils/isBuild'; diff --git a/packages/nextjs/test/config/wrappers.test.ts b/packages/nextjs/test/config/wrappers.test.ts index de7fe48b5d18..68c598e9707f 100644 --- a/packages/nextjs/test/config/wrappers.test.ts +++ b/packages/nextjs/test/config/wrappers.test.ts @@ -1,6 +1,5 @@ import * as SentryCore from '@sentry/core'; import * as SentryNode from '@sentry/node'; -import * as SentryTracing from '@sentry/tracing'; import type { IncomingMessage, ServerResponse } from 'http'; import { wrapGetInitialPropsWithSentry, wrapGetServerSidePropsWithSentry } from '../../src/server'; @@ -17,7 +16,7 @@ describe('data-fetching function wrappers', () => { req = { headers: {}, url: 'http://dogs.are.great/tricks/kangaroo' } as IncomingMessage; res = { end: jest.fn() } as unknown as ServerResponse; - jest.spyOn(SentryTracing, 'hasTracingEnabled').mockReturnValueOnce(true); + jest.spyOn(SentryCore, 'hasTracingEnabled').mockReturnValueOnce(true); jest.spyOn(SentryNode, 'getCurrentHub').mockReturnValueOnce({ getClient: () => ({ diff --git a/packages/nextjs/test/edge/edgeWrapperUtils.test.ts b/packages/nextjs/test/edge/edgeWrapperUtils.test.ts index 18b445e98191..852ceb5628b4 100644 --- a/packages/nextjs/test/edge/edgeWrapperUtils.test.ts +++ b/packages/nextjs/test/edge/edgeWrapperUtils.test.ts @@ -1,5 +1,4 @@ import * as coreSdk from '@sentry/core'; -import * as sentryTracing from '@sentry/tracing'; import { withEdgeWrapping } from '../../src/edge/utils/edgeWrapperUtils'; @@ -30,7 +29,7 @@ afterAll(() => { beforeEach(() => { jest.clearAllMocks(); jest.resetAllMocks(); - jest.spyOn(sentryTracing, 'hasTracingEnabled').mockImplementation(() => true); + jest.spyOn(coreSdk, 'hasTracingEnabled').mockImplementation(() => true); }); describe('withEdgeWrapping', () => { diff --git a/packages/nextjs/test/edge/withSentryAPI.test.ts b/packages/nextjs/test/edge/withSentryAPI.test.ts index 9c9c865c909f..2ecbdf22a96e 100644 --- a/packages/nextjs/test/edge/withSentryAPI.test.ts +++ b/packages/nextjs/test/edge/withSentryAPI.test.ts @@ -1,5 +1,4 @@ import * as coreSdk from '@sentry/core'; -import * as sentryTracing from '@sentry/tracing'; import { wrapApiHandlerWithSentry } from '../../src/edge'; @@ -32,7 +31,7 @@ afterAll(() => { beforeEach(() => { jest.resetAllMocks(); jest.restoreAllMocks(); - jest.spyOn(sentryTracing, 'hasTracingEnabled').mockImplementation(() => true); + jest.spyOn(coreSdk, 'hasTracingEnabled').mockImplementation(() => true); }); describe('wrapApiHandlerWithSentry', () => { diff --git a/packages/node/src/handlers.ts b/packages/node/src/handlers.ts index 1dfd8e4e27c9..4e54406704e6 100644 --- a/packages/node/src/handlers.ts +++ b/packages/node/src/handlers.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { captureException, getCurrentHub, startTransaction, withScope } from '@sentry/core'; +import { captureException, getCurrentHub, hasTracingEnabled, startTransaction, withScope } from '@sentry/core'; import type { Span } from '@sentry/types'; import type { AddRequestDataToEventOptions } from '@sentry/utils'; import { @@ -46,9 +46,7 @@ export function tracingHandler(): ( return next(); } - // TODO: This is the `hasTracingEnabled` check, but we're doing it manually since `@sentry/tracing` isn't a - // dependency of `@sentry/node`. Long term, that function should probably move to `@sentry/hub. - if (!('tracesSampleRate' in options) && !('tracesSampler' in options)) { + if (!hasTracingEnabled(options)) { __DEBUG_BUILD__ && logger.warn( 'Sentry `tracingHandler` is being used, but tracing is disabled. Please enable tracing by setting ' + diff --git a/packages/node/test/handlers.test.ts b/packages/node/test/handlers.test.ts index 9145305d6c1f..03cfba80ade4 100644 --- a/packages/node/test/handlers.test.ts +++ b/packages/node/test/handlers.test.ts @@ -1,5 +1,4 @@ import * as sentryCore from '@sentry/core'; -import { Hub, makeMain, Scope } from '@sentry/core'; import { Transaction } from '@sentry/tracing'; import type { Event } from '@sentry/types'; import { SentryError } from '@sentry/utils'; @@ -47,7 +46,7 @@ describe('requestHandler', () => { it('autoSessionTracking is enabled, sets requestSession status to ok, when handling a request', () => { const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.2' }); client = new NodeClient(options); - const hub = new Hub(client); + const hub = new sentryCore.Hub(client); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); @@ -60,7 +59,7 @@ describe('requestHandler', () => { it('autoSessionTracking is disabled, does not set requestSession, when handling a request', () => { const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '1.2' }); client = new NodeClient(options); - const hub = new Hub(client); + const hub = new sentryCore.Hub(client); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); @@ -73,7 +72,7 @@ describe('requestHandler', () => { it('autoSessionTracking is enabled, calls _captureRequestSession, on response finish', done => { const options = getDefaultNodeClientOptions({ autoSessionTracking: true, release: '1.2' }); client = new NodeClient(options); - const hub = new Hub(client); + const hub = new sentryCore.Hub(client); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); @@ -94,7 +93,7 @@ describe('requestHandler', () => { it('autoSessionTracking is disabled, does not call _captureRequestSession, on response finish', done => { const options = getDefaultNodeClientOptions({ autoSessionTracking: false, release: '1.2' }); client = new NodeClient(options); - const hub = new Hub(client); + const hub = new sentryCore.Hub(client); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); const captureRequestSession = jest.spyOn(client, '_captureRequestSession'); @@ -138,7 +137,7 @@ describe('requestHandler', () => { }); it('stores request and request data options in `sdkProcessingMetadata`', () => { - const hub = new Hub(new NodeClient(getDefaultNodeClientOptions())); + const hub = new sentryCore.Hub(new NodeClient(getDefaultNodeClientOptions())); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); const requestHandlerOptions = { include: { ip: false } }; @@ -165,7 +164,7 @@ describe('tracingHandler', () => { const sentryTracingMiddleware = tracingHandler(); - let hub: Hub, req: http.IncomingMessage, res: http.ServerResponse, next: () => undefined; + let hub: sentryCore.Hub, req: http.IncomingMessage, res: http.ServerResponse, next: () => undefined; function createNoOpSpy() { const noop = { noop: () => undefined }; // this is wrapped in an object so jest can spy on it @@ -173,7 +172,7 @@ describe('tracingHandler', () => { } beforeEach(() => { - hub = new Hub(new NodeClient(getDefaultNodeClientOptions({ tracesSampleRate: 1.0 }))); + hub = new sentryCore.Hub(new NodeClient(getDefaultNodeClientOptions({ tracesSampleRate: 1.0 }))); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); req = { headers, @@ -271,7 +270,7 @@ describe('tracingHandler', () => { it('extracts request data for sampling context', () => { const tracesSampler = jest.fn(); const options = getDefaultNodeClientOptions({ tracesSampler }); - const hub = new Hub(new NodeClient(options)); + const hub = new sentryCore.Hub(new NodeClient(options)); hub.run(() => { sentryTracingMiddleware(req, res, next); @@ -291,7 +290,7 @@ describe('tracingHandler', () => { it('puts its transaction on the scope', () => { const options = getDefaultNodeClientOptions({ tracesSampleRate: 1.0 }); - const hub = new Hub(new NodeClient(options)); + const hub = new sentryCore.Hub(new NodeClient(options)); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); @@ -411,7 +410,7 @@ describe('tracingHandler', () => { it('stores request in transaction metadata', () => { const options = getDefaultNodeClientOptions({ tracesSampleRate: 1.0 }); - const hub = new Hub(new NodeClient(options)); + const hub = new sentryCore.Hub(new NodeClient(options)); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); @@ -465,7 +464,7 @@ describe('errorHandler()', () => { client.initSessionFlusher(); const scope = sentryCore.getCurrentHub().getScope(); - const hub = new Hub(client); + const hub = new sentryCore.Hub(client); jest.spyOn(client, '_captureRequestSession'); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); @@ -481,7 +480,7 @@ describe('errorHandler()', () => { client = new NodeClient(options); const scope = sentryCore.getCurrentHub().getScope(); - const hub = new Hub(client); + const hub = new sentryCore.Hub(client); jest.spyOn(client, '_captureRequestSession'); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); @@ -498,8 +497,8 @@ describe('errorHandler()', () => { // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) client.initSessionFlusher(); - const scope = new Scope(); - const hub = new Hub(client, scope); + const scope = new sentryCore.Scope(); + const hub = new sentryCore.Hub(client, scope); jest.spyOn(client, '_captureRequestSession'); @@ -517,8 +516,8 @@ describe('errorHandler()', () => { // It is required to initialise SessionFlusher to capture Session Aggregates (it is usually initialised // by the`requestHandler`) client.initSessionFlusher(); - const scope = new Scope(); - const hub = new Hub(client, scope); + const scope = new sentryCore.Scope(); + const hub = new sentryCore.Hub(client, scope); jest.spyOn(client, '_captureRequestSession'); jest.spyOn(sentryCore, 'getCurrentHub').mockReturnValue(hub); @@ -532,12 +531,12 @@ describe('errorHandler()', () => { const options = getDefaultNodeClientOptions({}); client = new NodeClient(options); - const hub = new Hub(client); - makeMain(hub); + const hub = new sentryCore.Hub(client); + sentryCore.makeMain(hub); // `sentryErrorMiddleware` uses `withScope`, and we need access to the temporary scope it creates, so monkeypatch // `captureException` in order to examine the scope as it exists inside the `withScope` callback - hub.captureException = function (this: Hub, _exception: any) { + hub.captureException = function (this: sentryCore.Hub, _exception: any) { const scope = this.getScope(); expect((scope as any)._sdkProcessingMetadata.request).toEqual(req); } as any; diff --git a/packages/remix/src/utils/instrumentServer.ts b/packages/remix/src/utils/instrumentServer.ts index 9507fd28ec21..43e4d8cd1bf0 100644 --- a/packages/remix/src/utils/instrumentServer.ts +++ b/packages/remix/src/utils/instrumentServer.ts @@ -1,7 +1,8 @@ /* eslint-disable max-lines */ +import { hasTracingEnabled } from '@sentry/core'; import type { Hub } from '@sentry/node'; import { captureException, getCurrentHub } from '@sentry/node'; -import { getActiveTransaction, hasTracingEnabled } from '@sentry/tracing'; +import { getActiveTransaction } from '@sentry/tracing'; import type { Transaction, TransactionSource, WrappedFunction } from '@sentry/types'; import { addExceptionMechanism, diff --git a/packages/remix/src/utils/serverAdapters/express.ts b/packages/remix/src/utils/serverAdapters/express.ts index e2484e4a6d6d..59cb299e489b 100644 --- a/packages/remix/src/utils/serverAdapters/express.ts +++ b/packages/remix/src/utils/serverAdapters/express.ts @@ -1,6 +1,5 @@ -import { getCurrentHub } from '@sentry/core'; +import { getCurrentHub, hasTracingEnabled } from '@sentry/core'; import { flush } from '@sentry/node'; -import { hasTracingEnabled } from '@sentry/tracing'; import type { Transaction } from '@sentry/types'; import { extractRequestData, isString, logger } from '@sentry/utils'; import { cwd } from 'process'; diff --git a/packages/tracing/package.json b/packages/tracing/package.json index a7ad327d09e3..49c315a154e2 100644 --- a/packages/tracing/package.json +++ b/packages/tracing/package.json @@ -45,6 +45,7 @@ "lint": "run-s lint:prettier lint:eslint", "lint:eslint": "eslint . --format stylish", "lint:prettier": "prettier --check \"{src,test,scripts}/**/**.ts\"", + "test:unit": "jest", "test": "jest", "test:watch": "jest --watch" }, diff --git a/packages/tracing/src/browser/request.ts b/packages/tracing/src/browser/request.ts index 57d0178509ef..e1e1c9aaf7fb 100644 --- a/packages/tracing/src/browser/request.ts +++ b/packages/tracing/src/browser/request.ts @@ -1,5 +1,5 @@ /* eslint-disable max-lines */ -import { getCurrentHub } from '@sentry/core'; +import { getCurrentHub, hasTracingEnabled } from '@sentry/core'; import type { DynamicSamplingContext, Span } from '@sentry/types'; import { addInstrumentationHandler, @@ -9,8 +9,6 @@ import { stringMatchesSomePattern, } from '@sentry/utils'; -import { hasTracingEnabled } from '../utils'; - export const DEFAULT_TRACE_PROPAGATION_TARGETS = ['localhost', /^\//]; /** Options for Request Instrumentation */ diff --git a/packages/tracing/src/hubextensions.ts b/packages/tracing/src/hubextensions.ts index 9514cb00b321..443aac923119 100644 --- a/packages/tracing/src/hubextensions.ts +++ b/packages/tracing/src/hubextensions.ts @@ -1,5 +1,5 @@ import type { Hub } from '@sentry/core'; -import { getMainCarrier } from '@sentry/core'; +import { getMainCarrier, hasTracingEnabled } from '@sentry/core'; import type { ClientOptions, CustomSamplingContext, @@ -14,7 +14,6 @@ import { dynamicRequire, isNaN, isNodeEnv, loadModule, logger } from '@sentry/ut import { registerErrorInstrumentation } from './errors'; import { IdleTransaction } from './idletransaction'; import { Transaction } from './transaction'; -import { hasTracingEnabled } from './utils'; /** Returns all trace headers that are currently on the top scope. */ function traceHeaders(this: Hub): { [key: string]: string } { @@ -44,7 +43,7 @@ function traceHeaders(this: Hub): { [key: string]: string } { */ function sample( transaction: T, - options: Pick, + options: Pick, samplingContext: SamplingContext, ): T { // nothing to do if tracing is not enabled @@ -61,7 +60,7 @@ function sample( return transaction; } - // we would have bailed already if neither `tracesSampler` nor `tracesSampleRate` were defined, so one of these should + // we would have bailed already if neither `tracesSampler` nor `tracesSampleRate` nor `enableTracing` were defined, so one of these should // work; prefer the hook if so let sampleRate; if (typeof options.tracesSampler === 'function') { @@ -71,11 +70,17 @@ function sample( }); } else if (samplingContext.parentSampled !== undefined) { sampleRate = samplingContext.parentSampled; - } else { + } else if (typeof options.tracesSampleRate !== 'undefined') { sampleRate = options.tracesSampleRate; transaction.setMetadata({ sampleRate: Number(sampleRate), }); + } else { + // When `enableTracing === true`, we use a sample rate of 100% + sampleRate = 1; + transaction.setMetadata({ + sampleRate, + }); } // Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The diff --git a/packages/tracing/src/index.ts b/packages/tracing/src/index.ts index 14d80fb7b4cc..012c21ff825b 100644 --- a/packages/tracing/src/index.ts +++ b/packages/tracing/src/index.ts @@ -46,6 +46,7 @@ export { addExtensionMethods }; export { extractTraceparentData, getActiveTransaction, + // eslint-disable-next-line deprecation/deprecation hasTracingEnabled, stripUrlQueryAndFragment, TRACEPARENT_REGEXP, diff --git a/packages/tracing/src/utils.ts b/packages/tracing/src/utils.ts index 66cba77843b7..a34db3cf9ec7 100644 --- a/packages/tracing/src/utils.ts +++ b/packages/tracing/src/utils.ts @@ -1,5 +1,5 @@ import type { Hub } from '@sentry/core'; -import { getCurrentHub } from '@sentry/core'; +import { getCurrentHub, hasTracingEnabled as _hasTracingEnabled } from '@sentry/core'; import type { Options, Transaction } from '@sentry/types'; /** @@ -20,13 +20,12 @@ export { TRACEPARENT_REGEXP, extractTraceparentData } from '@sentry/utils'; * Determines if tracing is currently enabled. * * Tracing is enabled when at least one of `tracesSampleRate` and `tracesSampler` is defined in the SDK config. + * @deprecated This export has moved to `@sentry/core`. This export will be removed from `@sentry/tracing` in v8. */ export function hasTracingEnabled( - maybeOptions?: Pick | undefined, + maybeOptions?: Pick | undefined, ): boolean { - const client = getCurrentHub().getClient(); - const options = maybeOptions || (client && client.getOptions()); - return !!options && ('tracesSampleRate' in options || 'tracesSampler' in options); + return _hasTracingEnabled(maybeOptions); } /** Grabs active transaction off scope, if any */ diff --git a/packages/tracing/test/browser/request.test.ts b/packages/tracing/test/browser/request.test.ts index 3dd107d708dc..30a6ca3a2a9a 100644 --- a/packages/tracing/test/browser/request.test.ts +++ b/packages/tracing/test/browser/request.test.ts @@ -1,5 +1,5 @@ import { BrowserClient } from '@sentry/browser'; -import { Hub, makeMain } from '@sentry/core'; +import * as sentryCore from '@sentry/core'; import * as utils from '@sentry/utils'; import type { Transaction } from '../../src'; @@ -7,7 +7,6 @@ import { Span, spanStatusfromHttpCode } from '../../src'; import type { FetchData, XHRData } from '../../src/browser/request'; import { fetchCallback, instrumentOutgoingRequests, shouldAttachHeaders, xhrCallback } from '../../src/browser/request'; import { addExtensionMethods } from '../../src/hubextensions'; -import * as tracingUtils from '../../src/utils'; import { getDefaultBrowserClientOptions } from '../testutils'; beforeAll(() => { @@ -17,7 +16,7 @@ beforeAll(() => { global.Request = {}; }); -const hasTracingEnabled = jest.spyOn(tracingUtils, 'hasTracingEnabled'); +const hasTracingEnabled = jest.spyOn(sentryCore, 'hasTracingEnabled'); const addInstrumentationHandler = jest.spyOn(utils, 'addInstrumentationHandler'); const setRequestHeader = jest.fn(); @@ -47,7 +46,7 @@ describe('instrumentOutgoingRequests', () => { }); describe('callbacks', () => { - let hub: Hub; + let hub: sentryCore.Hub; let transaction: Transaction; const alwaysCreateSpan = () => true; const alwaysAttachHeaders = () => true; @@ -56,8 +55,8 @@ describe('callbacks', () => { beforeAll(() => { const options = getDefaultBrowserClientOptions({ tracesSampleRate: 1 }); - hub = new Hub(new BrowserClient(options)); - makeMain(hub); + hub = new sentryCore.Hub(new BrowserClient(options)); + sentryCore.makeMain(hub); }); beforeEach(() => { diff --git a/packages/tracing/test/hub.test.ts b/packages/tracing/test/hub.test.ts index 18c357066ff5..df183f2b95eb 100644 --- a/packages/tracing/test/hub.test.ts +++ b/packages/tracing/test/hub.test.ts @@ -189,6 +189,57 @@ describe('Hub', () => { expect(transaction.sampled).toBe(true); }); + it('should set sampled = true if enableTracing is true', () => { + const options = getDefaultBrowserClientOptions({ enableTracing: true }); + const hub = new Hub(new BrowserClient(options)); + makeMain(hub); + const transaction = hub.startTransaction({ name: 'dogpark' }); + + expect(transaction.sampled).toBe(true); + }); + + it('should set sampled = false if enableTracing is true & tracesSampleRate is 0', () => { + const options = getDefaultBrowserClientOptions({ enableTracing: true, tracesSampleRate: 0 }); + const hub = new Hub(new BrowserClient(options)); + makeMain(hub); + const transaction = hub.startTransaction({ name: 'dogpark' }); + + expect(transaction.sampled).toBe(false); + }); + + it('should set sampled = false if enableTracing is false & tracesSampleRate is 0', () => { + const options = getDefaultBrowserClientOptions({ enableTracing: false, tracesSampleRate: 0 }); + const hub = new Hub(new BrowserClient(options)); + makeMain(hub); + const transaction = hub.startTransaction({ name: 'dogpark' }); + + expect(transaction.sampled).toBe(false); + }); + + it('should prefer tracesSampler returning false to enableTracing', () => { + // make the two options do opposite things to prove precedence + const tracesSampler = jest.fn().mockReturnValue(false); + const options = getDefaultBrowserClientOptions({ enableTracing: true, tracesSampler }); + const hub = new Hub(new BrowserClient(options)); + makeMain(hub); + const transaction = hub.startTransaction({ name: 'dogpark' }); + + expect(tracesSampler).toHaveBeenCalled(); + expect(transaction.sampled).toBe(false); + }); + + it('should prefer tracesSampler returning true to enableTracing', () => { + // make the two options do opposite things to prove precedence + const tracesSampler = jest.fn().mockReturnValue(true); + const options = getDefaultBrowserClientOptions({ enableTracing: false, tracesSampler }); + const hub = new Hub(new BrowserClient(options)); + makeMain(hub); + const transaction = hub.startTransaction({ name: 'dogpark' }); + + expect(tracesSampler).toHaveBeenCalled(); + expect(transaction.sampled).toBe(true); + }); + it('should not try to override explicitly set positive sampling decision', () => { // so that the decision otherwise would be false const tracesSampler = jest.fn().mockReturnValue(0); diff --git a/packages/tracing/test/utils.test.ts b/packages/tracing/test/utils.test.ts index 3bd3e9f5f35f..95b58397ca66 100644 --- a/packages/tracing/test/utils.test.ts +++ b/packages/tracing/test/utils.test.ts @@ -1,17 +1,33 @@ import { extractTraceparentData, hasTracingEnabled } from '../src/utils'; -describe('hasTracingEnabled', () => { +describe('hasTracingEnabled (deprecated)', () => { const tracesSampler = () => 1; const tracesSampleRate = 1; it.each([ ['No options', undefined, false], - ['No tracesSampler or tracesSampleRate', {}, false], + ['No tracesSampler or tracesSampleRate or enableTracing', {}, false], ['With tracesSampler', { tracesSampler }, true], ['With tracesSampleRate', { tracesSampleRate }, true], + ['With enableTracing=true', { enableTracing: true }, true], + ['With enableTracing=false', { enableTracing: false }, false], + ['With tracesSampler && enableTracing=false', { tracesSampler, enableTracing: false }, true], + ['With tracesSampleRate && enableTracing=false', { tracesSampler, enableTracing: false }, true], ['With tracesSampler and tracesSampleRate', { tracesSampler, tracesSampleRate }, true], + [ + 'With tracesSampler and tracesSampleRate and enableTracing=true', + { tracesSampler, tracesSampleRate, enableTracing: true }, + true, + ], + [ + 'With tracesSampler and tracesSampleRate and enableTracing=false', + { tracesSampler, tracesSampleRate, enableTracing: false }, + true, + ], ])( '%s', + // eslint-disable-next-line deprecation/deprecation (_: string, input: Parameters[0], output: ReturnType) => { + // eslint-disable-next-line deprecation/deprecation expect(hasTracingEnabled(input)).toBe(output); }, ); diff --git a/packages/types/src/options.ts b/packages/types/src/options.ts index ca53b19f607a..0dce43ec4669 100644 --- a/packages/types/src/options.ts +++ b/packages/types/src/options.ts @@ -96,6 +96,13 @@ export interface ClientOptions { attachErrorHandler(app, options); - if ('tracesSampleRate' in options || 'tracesSampler' in options) { + if (hasTracingEnabled(options)) { app.mixin( createTracingMixins({ ...options,