diff --git a/packages/cloudflare/src/async.ts b/packages/cloudflare/src/async.ts index d7e6f909a4c9..bde7bbfc8f47 100644 --- a/packages/cloudflare/src/async.ts +++ b/packages/cloudflare/src/async.ts @@ -69,16 +69,7 @@ export function setAsyncLocalStorageAsyncContextStrategy(): void { }); } - // In contrast to the browser, we can rely on async context isolation here - function suppressTracing(callback: () => T): T { - return withScope(scope => { - scope.setSDKProcessingMetadata({ __SENTRY_SUPPRESS_TRACING__: true }); - return callback(); - }); - } - setAsyncContextStrategy({ - suppressTracing, withScope, withSetScope, withIsolationScope, diff --git a/packages/core/src/asyncContext/stackStrategy.ts b/packages/core/src/asyncContext/stackStrategy.ts index 36c1d2127530..e4b973d64e57 100644 --- a/packages/core/src/asyncContext/stackStrategy.ts +++ b/packages/core/src/asyncContext/stackStrategy.ts @@ -1,6 +1,7 @@ import type { Client } from '../client'; import { getDefaultCurrentScope, getDefaultIsolationScope } from '../defaultScopes'; import { Scope } from '../scope'; +import { SUPPRESS_TRACING_KEY } from '../tracing/constants'; import { chainAndCopyPromiseLike } from '../utils/chain-and-copy-promiselike'; import { isThenable } from '../utils/is'; import { getMainCarrier, getSentryCarrier } from './../carrier'; @@ -148,6 +149,7 @@ function withIsolationScope(callback: (isolationScope: Scope) => T): T { */ export function getStackAsyncContextStrategy(): AsyncContextStrategy { return { + suppressTracing: suppressTracingInStack, withIsolationScope, withScope, withSetScope, @@ -158,3 +160,19 @@ export function getStackAsyncContextStrategy(): AsyncContextStrategy { getIsolationScope: () => getAsyncContextStack().getIsolationScope(), }; } + +/** + * In stack-based ACS, we do not wait for the callback to finish before we reset the metadata + * the reason for this is that otherwise, in the stack this can lead to very weird behavior + * as there is only a single top scope, if the callback takes longer to finish, + * other, unrelated spans may also be suppressed, which we do not want + * so instead, we only suppress tracing synchronoysly in the stack. + */ +function suppressTracingInStack(callback: () => T): T { + return withScope(scope => { + scope.setSDKProcessingMetadata({ [SUPPRESS_TRACING_KEY]: true }); + const res = callback(); + scope.setSDKProcessingMetadata({ [SUPPRESS_TRACING_KEY]: undefined }); + return res; + }); +} diff --git a/packages/core/src/integrations/http/client-subscriptions.ts b/packages/core/src/integrations/http/client-subscriptions.ts index 4a80987f6918..19386f7d95f9 100644 --- a/packages/core/src/integrations/http/client-subscriptions.ts +++ b/packages/core/src/integrations/http/client-subscriptions.ts @@ -18,10 +18,10 @@ import { addOutgoingRequestBreadcrumb } from './add-outgoing-request-breadcrumb' import { bindScopeToEmitter, getSpanStatusFromHttpCode, + isTracingSuppressed, SPAN_STATUS_ERROR, SPAN_STATUS_UNSET, startInactiveSpan, - SUPPRESS_TRACING_KEY, withActiveSpan, } from '../../tracing'; import { debug } from '../../utils/debug-logger'; @@ -33,7 +33,7 @@ import type { HttpInstrumentationOptions, HttpClientRequest, HttpIncomingMessage import { DEBUG_BUILD } from '../../debug-build'; import { LOG_PREFIX, HTTP_ON_CLIENT_REQUEST } from './constants'; import type { ClientSubscriptionName } from './constants'; -import { getClient, getCurrentScope } from '../../currentScopes'; +import { getClient } from '../../currentScopes'; import { hasSpansEnabled } from '../../utils/hasSpansEnabled'; import { doubleWrapWarning } from './double-wrap-warning'; @@ -48,7 +48,7 @@ export function getHttpClientSubscriptions(options: HttpInstrumentationOptions): const onHttpClientRequestCreated: ChannelListener = (data: unknown): void => { // Skip all instrumentation if tracing is suppressed // (e.g., Sentry's own transport uses this to avoid self-instrumentation) - if (getCurrentScope().getScopeData().sdkProcessingMetadata[SUPPRESS_TRACING_KEY] === true) { + if (isTracingSuppressed()) { return; } diff --git a/packages/core/src/tracing/constants.ts b/packages/core/src/tracing/constants.ts new file mode 100644 index 000000000000..a3b7c4c1eae0 --- /dev/null +++ b/packages/core/src/tracing/constants.ts @@ -0,0 +1 @@ +export const SUPPRESS_TRACING_KEY = '__SENTRY_SUPPRESS_TRACING__'; diff --git a/packages/core/src/tracing/index.ts b/packages/core/src/tracing/index.ts index b71f13471c19..198b64ce16c4 100644 --- a/packages/core/src/tracing/index.ts +++ b/packages/core/src/tracing/index.ts @@ -32,7 +32,6 @@ export { isTracingSuppressed, startNewTrace, spanIsIgnored, - SUPPRESS_TRACING_KEY, } from './trace'; export { bindScopeToEmitter } from './bindScopeToEmitter'; export { diff --git a/packages/core/src/tracing/trace.ts b/packages/core/src/tracing/trace.ts index 7505b7889c1d..ff9e9b7f8d22 100644 --- a/packages/core/src/tracing/trace.ts +++ b/packages/core/src/tracing/trace.ts @@ -34,8 +34,7 @@ import { SentrySpan } from './sentrySpan'; import { SPAN_STATUS_ERROR } from './spanstatus'; import { setCapturedScopesOnSpan } from './utils'; import type { Client } from '../client'; - -export const SUPPRESS_TRACING_KEY = '__SENTRY_SUPPRESS_TRACING__'; +import { SUPPRESS_TRACING_KEY } from './constants'; /** * Wraps a function with a transaction/span and finishes the span after the function is done. @@ -293,15 +292,8 @@ export function suppressTracing(callback: () => T): T { } return withScope(scope => { - // Note: We do not wait for the callback to finish before we reset the metadata - // the reason for this is that otherwise, in the browser this can lead to very weird behavior - // as there is only a single top scope, if the callback takes longer to finish, - // other, unrelated spans may also be suppressed, which we do not want - // so instead, we only suppress tracing synchronoysly in the browser scope.setSDKProcessingMetadata({ [SUPPRESS_TRACING_KEY]: true }); - const res = callback(); - scope.setSDKProcessingMetadata({ [SUPPRESS_TRACING_KEY]: undefined }); - return res; + return callback(); }); } diff --git a/packages/core/test/lib/integrations/http/client-subscriptions.test.ts b/packages/core/test/lib/integrations/http/client-subscriptions.test.ts index 46b486368cdc..3ee3acf5b3f7 100644 --- a/packages/core/test/lib/integrations/http/client-subscriptions.test.ts +++ b/packages/core/test/lib/integrations/http/client-subscriptions.test.ts @@ -3,7 +3,7 @@ import * as breadcrumbModule from '../../../../src/integrations/http/add-outgoin import { HTTP_ON_CLIENT_REQUEST } from '../../../../src/integrations/http/constants'; import { getHttpClientSubscriptions } from '../../../../src/integrations/http/client-subscriptions'; import type { HttpClientRequest, HttpIncomingMessage } from '../../../../src/integrations/http/types'; -import { SUPPRESS_TRACING_KEY } from '../../../../src/tracing'; +import { SUPPRESS_TRACING_KEY } from '../../../../src/tracing/constants'; import { getCurrentScope, withScope } from '../../../../src/currentScopes'; function makeMockRequest(): HttpClientRequest & { diff --git a/packages/core/test/lib/tracing/trace.test.ts b/packages/core/test/lib/tracing/trace.test.ts index 891155652ed8..3f79ed862845 100644 --- a/packages/core/test/lib/tracing/trace.test.ts +++ b/packages/core/test/lib/tracing/trace.test.ts @@ -25,7 +25,6 @@ import { startInactiveSpan, startSpan, startSpanManual, - SUPPRESS_TRACING_KEY, suppressTracing, withActiveSpan, } from '../../../src/tracing'; @@ -37,6 +36,7 @@ import type { StartSpanOptions } from '../../../src/types/startSpanOptions'; import { _setSpanForScope } from '../../../src/utils/spanOnScope'; import { getActiveSpan, getRootSpan, getSpanDescendants, spanIsSampled } from '../../../src/utils/spanUtils'; import { getDefaultTestClientOptions, TestClient } from '../../mocks/client'; +import { SUPPRESS_TRACING_KEY } from '../../../src/tracing/constants'; const enum Type { Sync = 'sync', diff --git a/packages/deno/src/async.ts b/packages/deno/src/async.ts index 302bdce9d702..425905216162 100644 --- a/packages/deno/src/async.ts +++ b/packages/deno/src/async.ts @@ -77,16 +77,7 @@ export function setAsyncLocalStorageAsyncContextStrategy(): void { }); } - // In contrast to the browser, we can rely on async context isolation here - function suppressTracing(callback: () => T): T { - return withScope(scope => { - scope.setSDKProcessingMetadata({ __SENTRY_SUPPRESS_TRACING__: true }); - return callback(); - }); - } - setAsyncContextStrategy({ - suppressTracing, withScope, withSetScope, withIsolationScope, diff --git a/packages/node/src/integrations/fs/index.ts b/packages/node/src/integrations/fs/index.ts index 6dcd4352a1c9..35835fe9ea8d 100644 --- a/packages/node/src/integrations/fs/index.ts +++ b/packages/node/src/integrations/fs/index.ts @@ -17,7 +17,10 @@ export const fsIntegration = defineIntegration((options: FsInstrumentationConfig return { name: INTEGRATION_NAME, setupOnce() { - enableFsInstrumentation(options); + // We only run this in the next tick to avoid instrumenting the `fs` module while the SDK is initializating + // especially, at the point when this runs the Async Context Manager may not be set up yet, + // which could lead to weird outcomes - so we wait until everything is settled before we instrument. + setImmediate(() => enableFsInstrumentation(options)); }, }; }); diff --git a/packages/opentelemetry/src/asyncContextStrategy.ts b/packages/opentelemetry/src/asyncContextStrategy.ts index 71c9e41f5f77..38b19048f8c1 100644 --- a/packages/opentelemetry/src/asyncContextStrategy.ts +++ b/packages/opentelemetry/src/asyncContextStrategy.ts @@ -11,7 +11,6 @@ import type { CurrentScopes } from './types'; import { getContextFromScope, getScopesFromContext } from './utils/contextData'; import { getActiveSpan } from './utils/getActiveSpan'; import { getTraceData } from './utils/getTraceData'; -import { suppressTracing, isTracingSuppressed } from './utils/suppressTracing'; /** * Sets the async context strategy to use follow the OTEL context under the hood. @@ -103,8 +102,6 @@ export function setOpenTelemetryContextAsyncContextStrategy(options?: { startSpanManual, startInactiveSpan, getActiveSpan, - suppressTracing, - isTracingSuppressed, getTraceData, continueTrace, startNewTrace, diff --git a/packages/opentelemetry/src/exports.ts b/packages/opentelemetry/src/exports.ts index ed8333207de6..af4435c8b493 100644 --- a/packages/opentelemetry/src/exports.ts +++ b/packages/opentelemetry/src/exports.ts @@ -4,8 +4,6 @@ export { enhanceDscWithOpenTelemetryRootSpanName } from './utils/enhanceDscWithO export { getTraceContextForScope } from './trace'; -export { suppressTracing } from './utils/suppressTracing'; - export { setupEventContextTrace } from './setupEventContextTrace'; // eslint-disable-next-line typescript/no-deprecated diff --git a/packages/opentelemetry/src/utils/suppressTracing.ts b/packages/opentelemetry/src/utils/suppressTracing.ts deleted file mode 100644 index f37fadee3d4c..000000000000 --- a/packages/opentelemetry/src/utils/suppressTracing.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { context } from '@opentelemetry/api'; -import { - suppressTracing as suppressTracingImpl, - isTracingSuppressed as isTracingSuppressedImpl, -} from '@opentelemetry/core'; -import type { Scope } from '@sentry/core'; -import { getContextFromScope } from './contextData'; - -/** Suppress tracing in the given callback, ensuring no spans are generated inside of it. */ -export function suppressTracing(callback: () => T): T { - const ctx = suppressTracingImpl(context.active()); - return context.with(ctx, callback); -} - -export function isTracingSuppressed(scope?: Scope): boolean { - const ctx = scope ? getContextFromScope(scope) : context.active(); - return ctx ? isTracingSuppressedImpl(ctx) : false; -}