Skip to content

Commit 15d6cb1

Browse files
authored
feat(core): Implement suppressTracing (#11468)
This implements a new `suppressTracing` method which can be used to ensure we do not send any spans inside of a callback. Usage: ```js await suppressTracing(() => { // do not capture any spans for this fetch! return fetch(...); }); ``` This also changes the behavior of starting child spans slightly to actually create `NonRecordingSpan` as child spans when not sampled (previously, we'd still create SentrySpans). I choose to continue to create proper spans for unsampled transactions because it makes things like picking name and stuff like this easier for the time being.
1 parent a1bb8d0 commit 15d6cb1

File tree

14 files changed

+225
-65
lines changed

14 files changed

+225
-65
lines changed

packages/browser-utils/test/browser/metrics/index.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const originalLocation = WINDOW.location;
3232
const resourceEntryName = 'https://example.com/assets/to/css';
3333

3434
describe('_addMeasureSpans', () => {
35-
const span = new SentrySpan({ op: 'pageload', name: '/' });
35+
const span = new SentrySpan({ op: 'pageload', name: '/', sampled: true });
3636

3737
beforeEach(() => {
3838
getCurrentScope().clear();
@@ -86,7 +86,7 @@ describe('_addMeasureSpans', () => {
8686
});
8787

8888
describe('_addResourceSpans', () => {
89-
const span = new SentrySpan({ op: 'pageload', name: '/' });
89+
const span = new SentrySpan({ op: 'pageload', name: '/', sampled: true });
9090

9191
beforeAll(() => {
9292
setGlobalLocation(mockWindowLocation);

packages/browser-utils/test/browser/metrics/utils.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ describe('startAndEndSpan()', () => {
2626
});
2727

2828
it('creates a span with given properties', () => {
29-
const parentSpan = new SentrySpan({ name: 'test' });
29+
const parentSpan = new SentrySpan({ name: 'test', sampled: true });
3030
const span = startAndEndSpan(parentSpan, 100, 200, {
3131
name: 'evaluation',
3232
op: 'script',
@@ -40,7 +40,7 @@ describe('startAndEndSpan()', () => {
4040
});
4141

4242
it('adjusts the start timestamp if child span starts before transaction', () => {
43-
const parentSpan = new SentrySpan({ name: 'test', startTimestamp: 123 });
43+
const parentSpan = new SentrySpan({ name: 'test', startTimestamp: 123, sampled: true });
4444
const span = startAndEndSpan(parentSpan, 100, 200, {
4545
name: 'script.js',
4646
op: 'resource',
@@ -52,7 +52,7 @@ describe('startAndEndSpan()', () => {
5252
});
5353

5454
it('does not adjust start timestamp if child span starts after transaction', () => {
55-
const parentSpan = new SentrySpan({ name: 'test', startTimestamp: 123 });
55+
const parentSpan = new SentrySpan({ name: 'test', startTimestamp: 123, sampled: true });
5656
const span = startAndEndSpan(parentSpan, 150, 200, {
5757
name: 'script.js',
5858
op: 'resource',

packages/bun/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"dependencies": {
4545
"@sentry/core": "8.0.0-alpha.9",
4646
"@sentry/node": "8.0.0-alpha.9",
47+
"@sentry/opentelemetry": "8.0.0-alpha.9",
4748
"@sentry/types": "8.0.0-alpha.9",
4849
"@sentry/utils": "8.0.0-alpha.9"
4950
},

packages/bun/src/transports/index.ts

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createTransport } from '@sentry/core';
1+
import { createTransport, suppressTracing } from '@sentry/core';
22
import type { BaseTransportOptions, Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/types';
33
import { rejectedSyncPromise } from '@sentry/utils';
44

@@ -19,14 +19,16 @@ export function makeFetchTransport(options: BunTransportOptions): Transport {
1919
};
2020

2121
try {
22-
return fetch(options.url, requestOptions).then(response => {
23-
return {
24-
statusCode: response.status,
25-
headers: {
26-
'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
27-
'retry-after': response.headers.get('Retry-After'),
28-
},
29-
};
22+
return suppressTracing(() => {
23+
return fetch(options.url, requestOptions).then(response => {
24+
return {
25+
statusCode: response.status,
26+
headers: {
27+
'x-sentry-rate-limits': response.headers.get('X-Sentry-Rate-Limits'),
28+
'retry-after': response.headers.get('Retry-After'),
29+
},
30+
};
31+
});
3032
});
3133
} catch (e) {
3234
return rejectedSyncPromise(e);

packages/core/src/asyncContext.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { Hub, Integration } from '@sentry/types';
22
import type { Scope } from '@sentry/types';
33
import { GLOBAL_OBJ } from '@sentry/utils';
4-
import type { startInactiveSpan, startSpan, startSpanManual, withActiveSpan } from './tracing/trace';
4+
import type { startInactiveSpan, startSpan, startSpanManual, suppressTracing, withActiveSpan } from './tracing/trace';
55
import type { getActiveSpan } from './utils/spanUtils';
66

77
/**
@@ -62,6 +62,9 @@ export interface AsyncContextStrategy {
6262

6363
/** Make a span the active span in the context of the callback. */
6464
withActiveSpan?: typeof withActiveSpan;
65+
66+
/** Suppress tracing in the given callback, ensuring no spans are generated inside of it. */
67+
suppressTracing?: typeof suppressTracing;
6568
}
6669

6770
/**

packages/core/src/tracing/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export {
1414
startSpanManual,
1515
continueTrace,
1616
withActiveSpan,
17+
suppressTracing,
1718
} from './trace';
1819
export { getDynamicSamplingContextFromClient, getDynamicSamplingContextFromSpan } from './dynamicSamplingContext';
1920
export { setMeasurement, timedEventsToMeasurements } from './measurement';

packages/core/src/tracing/trace.ts

Lines changed: 47 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import type { ClientOptions, Scope, SentrySpanArguments, Span, SpanTimeInput, St
33
import { propagationContextFromHeaders } from '@sentry/utils';
44
import type { AsyncContextStrategy } from '../asyncContext';
55
import { getMainCarrier } from '../asyncContext';
6+
67
import { getClient, getCurrentScope, getIsolationScope, withScope } from '../currentScopes';
78

89
import { getAsyncContextStrategy } from '../hub';
@@ -25,6 +26,8 @@ import { SentrySpan } from './sentrySpan';
2526
import { SPAN_STATUS_ERROR } from './spanstatus';
2627
import { setCapturedScopesOnSpan } from './utils';
2728

29+
const SUPPRESS_TRACING_KEY = '__SENTRY_SUPPRESS_TRACING__';
30+
2831
/**
2932
* Wraps a function with a transaction/span and finishes the span after the function is done.
3033
* The created span is the active span and will be used as parent by other spans created inside the function
@@ -204,6 +207,20 @@ export function withActiveSpan<T>(span: Span | null, callback: (scope: Scope) =>
204207
});
205208
}
206209

210+
/** Suppress tracing in the given callback, ensuring no spans are generated inside of it. */
211+
export function suppressTracing<T>(callback: () => T): T {
212+
const acs = getAcs();
213+
214+
if (acs.suppressTracing) {
215+
return acs.suppressTracing(callback);
216+
}
217+
218+
return withScope(scope => {
219+
scope.setSDKProcessingMetadata({ [SUPPRESS_TRACING_KEY]: true });
220+
return callback();
221+
});
222+
}
223+
207224
function createChildSpanOrTransaction({
208225
parentSpan,
209226
spanContext,
@@ -223,7 +240,7 @@ function createChildSpanOrTransaction({
223240

224241
let span: Span;
225242
if (parentSpan && !forceTransaction) {
226-
span = _startChildSpan(parentSpan, spanContext);
243+
span = _startChildSpan(parentSpan, scope, spanContext);
227244
addChildSpanToSpan(parentSpan, span);
228245
} else if (parentSpan) {
229246
// If we forced a transaction but have a parent span, make sure to continue from the parent span, not the scope
@@ -237,6 +254,7 @@ function createChildSpanOrTransaction({
237254
parentSpanId,
238255
...spanContext,
239256
},
257+
scope,
240258
parentSampled,
241259
);
242260

@@ -258,6 +276,7 @@ function createChildSpanOrTransaction({
258276
parentSpanId,
259277
...spanContext,
260278
},
279+
scope,
261280
parentSampled,
262281
);
263282

@@ -296,22 +315,24 @@ function getAcs(): AsyncContextStrategy {
296315
return getAsyncContextStrategy(carrier);
297316
}
298317

299-
function _startRootSpan(spanArguments: SentrySpanArguments, parentSampled?: boolean): SentrySpan {
318+
function _startRootSpan(spanArguments: SentrySpanArguments, scope: Scope, parentSampled?: boolean): SentrySpan {
300319
const client = getClient();
301320
const options: Partial<ClientOptions> = (client && client.getOptions()) || {};
302321

303322
const { name = '', attributes } = spanArguments;
304-
const [sampled, sampleRate] = sampleSpan(options, {
305-
name,
306-
parentSampled,
307-
attributes,
308-
transactionContext: {
309-
name,
310-
parentSampled,
311-
},
312-
});
313-
314-
const transaction = new SentrySpan({
323+
const [sampled, sampleRate] = scope.getScopeData().sdkProcessingMetadata[SUPPRESS_TRACING_KEY]
324+
? [false]
325+
: sampleSpan(options, {
326+
name,
327+
parentSampled,
328+
attributes,
329+
transactionContext: {
330+
name,
331+
parentSampled,
332+
},
333+
});
334+
335+
const rootSpan = new SentrySpan({
315336
...spanArguments,
316337
attributes: {
317338
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
@@ -320,30 +341,32 @@ function _startRootSpan(spanArguments: SentrySpanArguments, parentSampled?: bool
320341
sampled,
321342
});
322343
if (sampleRate !== undefined) {
323-
transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, sampleRate);
344+
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, sampleRate);
324345
}
325346

326347
if (client) {
327-
client.emit('spanStart', transaction);
348+
client.emit('spanStart', rootSpan);
328349
}
329350

330-
return transaction;
351+
return rootSpan;
331352
}
332353

333354
/**
334355
* Creates a new `Span` while setting the current `Span.id` as `parentSpanId`.
335356
* This inherits the sampling decision from the parent span.
336357
*/
337-
function _startChildSpan(parentSpan: Span, spanArguments: SentrySpanArguments): SentrySpan {
358+
function _startChildSpan(parentSpan: Span, scope: Scope, spanArguments: SentrySpanArguments): Span {
338359
const { spanId, traceId } = parentSpan.spanContext();
339-
const sampled = spanIsSampled(parentSpan);
360+
const sampled = scope.getScopeData().sdkProcessingMetadata[SUPPRESS_TRACING_KEY] ? false : spanIsSampled(parentSpan);
340361

341-
const childSpan = new SentrySpan({
342-
...spanArguments,
343-
parentSpanId: spanId,
344-
traceId,
345-
sampled,
346-
});
362+
const childSpan = sampled
363+
? new SentrySpan({
364+
...spanArguments,
365+
parentSpanId: spanId,
366+
traceId,
367+
sampled,
368+
})
369+
: new SentryNonRecordingSpan();
347370

348371
addChildSpanToSpan(parentSpan, childSpan);
349372

0 commit comments

Comments
 (0)