Skip to content

Commit 65d4c30

Browse files
committed
properly implement suppressTracing
1 parent 34f79d7 commit 65d4c30

File tree

9 files changed

+152
-32
lines changed

9 files changed

+152
-32
lines changed

packages/bun/src/transports/index.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { createTransport } from '@sentry/core';
2-
import { suppressTracing } from '@sentry/opentelemetry';
1+
import { createTransport, suppressTracing } from '@sentry/core';
32
import type { BaseTransportOptions, Transport, TransportMakeRequestResponse, TransportRequest } from '@sentry/types';
43
import { rejectedSyncPromise } from '@sentry/utils';
54

packages/core/src/constants.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1 @@
11
export const DEFAULT_ENVIRONMENT = 'production';
2-
3-
export const SUPPRESS_TRACING_KEY = '__SENTRY_SUPPRESS_TRACING__';

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: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +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-
import { SUPPRESS_TRACING_KEY } from '../constants';
6+
77
import { getClient, getCurrentScope, getIsolationScope, withScope } from '../currentScopes';
88

99
import { getAsyncContextStrategy } from '../hub';
@@ -26,6 +26,8 @@ import { SentrySpan } from './sentrySpan';
2626
import { SPAN_STATUS_ERROR } from './spanstatus';
2727
import { setCapturedScopesOnSpan } from './utils';
2828

29+
const SUPPRESS_TRACING_KEY = '__SENTRY_SUPPRESS_TRACING__';
30+
2931
/**
3032
* Wraps a function with a transaction/span and finishes the span after the function is done.
3133
* The created span is the active span and will be used as parent by other spans created inside the function
@@ -238,7 +240,7 @@ function createChildSpanOrTransaction({
238240

239241
let span: Span;
240242
if (parentSpan && !forceTransaction) {
241-
span = _startChildSpan(parentSpan, spanContext);
243+
span = _startChildSpan(parentSpan, scope, spanContext);
242244
addChildSpanToSpan(parentSpan, span);
243245
} else if (parentSpan) {
244246
// If we forced a transaction but have a parent span, make sure to continue from the parent span, not the scope
@@ -330,7 +332,7 @@ function _startRootSpan(spanArguments: SentrySpanArguments, scope: Scope, parent
330332
},
331333
});
332334

333-
const transaction = new SentrySpan({
335+
const rootSpan = new SentrySpan({
334336
...spanArguments,
335337
attributes: {
336338
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
@@ -339,30 +341,32 @@ function _startRootSpan(spanArguments: SentrySpanArguments, scope: Scope, parent
339341
sampled,
340342
});
341343
if (sampleRate !== undefined) {
342-
transaction.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, sampleRate);
344+
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, sampleRate);
343345
}
344346

345347
if (client) {
346-
client.emit('spanStart', transaction);
348+
client.emit('spanStart', rootSpan);
347349
}
348350

349-
return transaction;
351+
return rootSpan;
350352
}
351353

352354
/**
353355
* Creates a new `Span` while setting the current `Span.id` as `parentSpanId`.
354356
* This inherits the sampling decision from the parent span.
355357
*/
356-
function _startChildSpan(parentSpan: Span, spanArguments: SentrySpanArguments): SentrySpan {
358+
function _startChildSpan(parentSpan: Span, scope: Scope, spanArguments: SentrySpanArguments): Span {
357359
const { spanId, traceId } = parentSpan.spanContext();
358-
const sampled = spanIsSampled(parentSpan);
360+
const sampled = scope.getScopeData().sdkProcessingMetadata[SUPPRESS_TRACING_KEY] ? false : spanIsSampled(parentSpan);
359361

360-
const childSpan = new SentrySpan({
361-
...spanArguments,
362-
parentSpanId: spanId,
363-
traceId,
364-
sampled,
365-
});
362+
const childSpan = sampled
363+
? new SentrySpan({
364+
...spanArguments,
365+
parentSpanId: spanId,
366+
traceId,
367+
sampled,
368+
})
369+
: new SentryNonRecordingSpan();
366370

367371
addChildSpanToSpan(parentSpan, childSpan);
368372

packages/core/test/lib/tracing/trace.test.ts

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ import {
2020
startInactiveSpan,
2121
startSpan,
2222
startSpanManual,
23+
suppressTracing,
2324
withActiveSpan,
2425
} from '../../../src/tracing';
2526
import { SentryNonRecordingSpan } from '../../../src/tracing/sentryNonRecordingSpan';
2627
import { _setSpanForScope } from '../../../src/utils/spanOnScope';
27-
import { getActiveSpan, getRootSpan, getSpanDescendants } from '../../../src/utils/spanUtils';
28+
import { getActiveSpan, getRootSpan, getSpanDescendants, spanIsSampled } from '../../../src/utils/spanUtils';
2829
import { TestClient, getDefaultTestClientOptions } from '../../mocks/client';
2930

3031
beforeAll(() => {
@@ -259,7 +260,7 @@ describe('startSpan', () => {
259260
const initialScope = getCurrentScope();
260261

261262
const manualScope = initialScope.clone();
262-
const parentSpan = new SentrySpan({ spanId: 'parent-span-id' });
263+
const parentSpan = new SentrySpan({ spanId: 'parent-span-id', sampled: true });
263264
_setSpanForScope(manualScope, parentSpan);
264265

265266
startSpan({ name: 'GET users/[id]', scope: manualScope }, span => {
@@ -490,7 +491,7 @@ describe('startSpan', () => {
490491
});
491492

492493
it('uses implementation from ACS, if it exists', () => {
493-
const staticSpan = new SentrySpan({ spanId: 'aha' });
494+
const staticSpan = new SentrySpan({ spanId: 'aha', sampled: true });
494495

495496
const carrier = getMainCarrier();
496497

@@ -575,7 +576,7 @@ describe('startSpanManual', () => {
575576
const initialScope = getCurrentScope();
576577

577578
const manualScope = initialScope.clone();
578-
const parentSpan = new SentrySpan({ spanId: 'parent-span-id' });
579+
const parentSpan = new SentrySpan({ spanId: 'parent-span-id', sampled: true });
579580
_setSpanForScope(manualScope, parentSpan);
580581

581582
startSpanManual({ name: 'GET users/[id]', scope: manualScope }, (span, finish) => {
@@ -761,7 +762,7 @@ describe('startSpanManual', () => {
761762
});
762763

763764
it('uses implementation from ACS, if it exists', () => {
764-
const staticSpan = new SentrySpan({ spanId: 'aha' });
765+
const staticSpan = new SentrySpan({ spanId: 'aha', sampled: true });
765766

766767
const carrier = getMainCarrier();
767768

@@ -840,7 +841,7 @@ describe('startInactiveSpan', () => {
840841
const initialScope = getCurrentScope();
841842

842843
const manualScope = initialScope.clone();
843-
const parentSpan = new SentrySpan({ spanId: 'parent-span-id' });
844+
const parentSpan = new SentrySpan({ spanId: 'parent-span-id', sampled: true });
844845
_setSpanForScope(manualScope, parentSpan);
845846

846847
const span = startInactiveSpan({ name: 'GET users/[id]', scope: manualScope });
@@ -994,7 +995,7 @@ describe('startInactiveSpan', () => {
994995
});
995996
});
996997

997-
it('includes the scope at the time the span was started when finished xxx', async () => {
998+
it('includes the scope at the time the span was started when finished', async () => {
998999
const beforeSendTransaction = jest.fn(event => event);
9991000

10001001
const client = new TestClient(
@@ -1048,7 +1049,7 @@ describe('startInactiveSpan', () => {
10481049
});
10491050

10501051
it('uses implementation from ACS, if it exists', () => {
1051-
const staticSpan = new SentrySpan({ spanId: 'aha' });
1052+
const staticSpan = new SentrySpan({ spanId: 'aha', sampled: true });
10521053

10531054
const carrier = getMainCarrier();
10541055

@@ -1206,7 +1207,7 @@ describe('getActiveSpan', () => {
12061207
});
12071208

12081209
it('works with an active span on the scope', () => {
1209-
const activeSpan = new SentrySpan({ spanId: 'aha' });
1210+
const activeSpan = new SentrySpan({ spanId: 'aha', sampled: true });
12101211

12111212
withActiveSpan(activeSpan, () => {
12121213
const span = getActiveSpan();
@@ -1215,7 +1216,7 @@ describe('getActiveSpan', () => {
12151216
});
12161217

12171218
it('uses implementation from ACS, if it exists', () => {
1218-
const staticSpan = new SentrySpan({ spanId: 'aha' });
1219+
const staticSpan = new SentrySpan({ spanId: 'aha', sampled: true });
12191220

12201221
const carrier = getMainCarrier();
12211222

@@ -1285,7 +1286,7 @@ describe('withActiveSpan()', () => {
12851286
});
12861287

12871288
it('uses implementation from ACS, if it exists', () => {
1288-
const staticSpan = new SentrySpan({ spanId: 'aha' });
1289+
const staticSpan = new SentrySpan({ spanId: 'aha', sampled: true });
12891290
const staticScope = new Scope();
12901291

12911292
const carrier = getMainCarrier();
@@ -1359,3 +1360,66 @@ describe('span hooks', () => {
13591360
expect(endedSpans).toEqual(['span5', 'span3', 'span2', 'span1']);
13601361
});
13611362
});
1363+
1364+
describe('suppressTracing', () => {
1365+
beforeEach(() => {
1366+
addTracingExtensions();
1367+
1368+
getCurrentScope().clear();
1369+
getIsolationScope().clear();
1370+
getGlobalScope().clear();
1371+
1372+
setAsyncContextStrategy(undefined);
1373+
1374+
const options = getDefaultTestClientOptions({ tracesSampleRate: 1 });
1375+
client = new TestClient(options);
1376+
setCurrentClient(client);
1377+
client.init();
1378+
});
1379+
1380+
afterEach(() => {
1381+
jest.clearAllMocks();
1382+
});
1383+
1384+
it('works for a root span', () => {
1385+
const span = suppressTracing(() => {
1386+
return startInactiveSpan({ name: 'span' });
1387+
});
1388+
1389+
expect(span.isRecording()).toBe(false);
1390+
expect(spanIsSampled(span)).toBe(false);
1391+
});
1392+
1393+
it('works for a child span', () => {
1394+
startSpan({ name: 'outer' }, span => {
1395+
expect(span.isRecording()).toBe(true);
1396+
expect(spanIsSampled(span)).toBe(true);
1397+
1398+
const child1 = startInactiveSpan({ name: 'inner1' });
1399+
1400+
expect(child1.isRecording()).toBe(true);
1401+
expect(spanIsSampled(child1)).toBe(true);
1402+
1403+
const child2 = suppressTracing(() => {
1404+
return startInactiveSpan({ name: 'span' });
1405+
});
1406+
1407+
expect(child2.isRecording()).toBe(false);
1408+
expect(spanIsSampled(child2)).toBe(false);
1409+
});
1410+
});
1411+
1412+
it('works for a child span with forceTransaction=true', () => {
1413+
startSpan({ name: 'outer' }, span => {
1414+
expect(span.isRecording()).toBe(true);
1415+
expect(spanIsSampled(span)).toBe(true);
1416+
1417+
const child = suppressTracing(() => {
1418+
return startInactiveSpan({ name: 'span', forceTransaction: true });
1419+
});
1420+
1421+
expect(child.isRecording()).toBe(false);
1422+
expect(spanIsSampled(child)).toBe(false);
1423+
});
1424+
});
1425+
});

packages/nextjs/src/common/devErrorSymbolicationEventProcessor.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { suppressTracing } from '@sentry/opentelemetry';
1+
import { suppressTracing } from '@sentry/core';
22
import type { Event, EventHint } from '@sentry/types';
33
import { GLOBAL_OBJ } from '@sentry/utils';
44
import type { StackFrame } from 'stacktrace-parser';

packages/node/src/transports/http.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ import * as http from 'node:http';
22
import * as https from 'node:https';
33
import { Readable } from 'stream';
44
import { createGzip } from 'zlib';
5-
import { createTransport } from '@sentry/core';
6-
import { suppressTracing } from '@sentry/opentelemetry';
5+
import { createTransport, suppressTracing } from '@sentry/core';
76
import type {
87
BaseTransportOptions,
98
Transport,

packages/opentelemetry/src/asyncContextStrategy.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { startInactiveSpan, startSpan, startSpanManual, withActiveSpan } from '.
1313
import type { CurrentScopes } from './types';
1414
import { getScopesFromContext } from './utils/contextData';
1515
import { getActiveSpan } from './utils/getActiveSpan';
16+
import { suppressTracing } from './utils/suppressTracing';
1617

1718
/**
1819
* Sets the async context strategy to use follow the OTEL context under the hood.
@@ -122,5 +123,6 @@ export function setOpenTelemetryContextAsyncContextStrategy(): void {
122123
// The types here don't fully align, because our own `Span` type is narrower
123124
// than the OTEL one - but this is OK for here, as we now we'll only have OTEL spans passed around
124125
withActiveSpan: withActiveSpan as typeof defaultWithActiveSpan,
126+
suppressTracing: suppressTracing,
125127
});
126128
}

packages/opentelemetry/test/trace.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
getRootSpan,
1616
spanIsSampled,
1717
spanToJSON,
18+
suppressTracing,
1819
withScope,
1920
} from '@sentry/core';
2021
import type { Event, Scope } from '@sentry/types';
@@ -1575,6 +1576,58 @@ describe('continueTrace', () => {
15751576
});
15761577
});
15771578

1579+
describe('suppressTracing', () => {
1580+
beforeEach(() => {
1581+
mockSdkInit({ enableTracing: true });
1582+
});
1583+
1584+
afterEach(() => {
1585+
cleanupOtel();
1586+
});
1587+
1588+
it('works for a root span', () => {
1589+
const span = suppressTracing(() => {
1590+
return startInactiveSpan({ name: 'span' });
1591+
});
1592+
1593+
expect(span.isRecording()).toBe(false);
1594+
expect(spanIsSampled(span)).toBe(false);
1595+
});
1596+
1597+
it('works for a child span', () => {
1598+
startSpan({ name: 'outer' }, span => {
1599+
expect(span.isRecording()).toBe(true);
1600+
expect(spanIsSampled(span)).toBe(true);
1601+
1602+
const child1 = startInactiveSpan({ name: 'inner1' });
1603+
1604+
expect(child1.isRecording()).toBe(true);
1605+
expect(spanIsSampled(child1)).toBe(true);
1606+
1607+
const child2 = suppressTracing(() => {
1608+
return startInactiveSpan({ name: 'span' });
1609+
});
1610+
1611+
expect(child2.isRecording()).toBe(false);
1612+
expect(spanIsSampled(child2)).toBe(false);
1613+
});
1614+
});
1615+
1616+
it('works for a child span with forceTransaction=true', () => {
1617+
startSpan({ name: 'outer' }, span => {
1618+
expect(span.isRecording()).toBe(true);
1619+
expect(spanIsSampled(span)).toBe(true);
1620+
1621+
const child = suppressTracing(() => {
1622+
return startInactiveSpan({ name: 'span', forceTransaction: true });
1623+
});
1624+
1625+
expect(child.isRecording()).toBe(false);
1626+
expect(spanIsSampled(child)).toBe(false);
1627+
});
1628+
});
1629+
});
1630+
15781631
function getSpanName(span: AbstractSpan): string | undefined {
15791632
return spanHasName(span) ? span.name : undefined;
15801633
}

0 commit comments

Comments
 (0)