Skip to content

Commit a3e08ad

Browse files
authored
feat(core): Add inheritOrSampleWith helper to traceSampler (#15277)
1 parent 76854e8 commit a3e08ad

File tree

7 files changed

+63
-13
lines changed

7 files changed

+63
-13
lines changed

dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/server.js

+2-6
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,8 @@ const Sentry = require('@sentry/node');
44
Sentry.init({
55
dsn: 'https://[email protected]/1337',
66
transport: loggingTransport,
7-
tracesSampler: ({ parentSampleRate }) => {
8-
if (parentSampleRate) {
9-
return parentSampleRate;
10-
}
11-
12-
return 0.69;
7+
tracesSampler: ({ inheritOrSampleWith }) => {
8+
return inheritOrSampleWith(0.69);
139
},
1410
});
1511

dev-packages/node-integration-tests/suites/tracing/sample-rate-propagation/tracesSampler/test.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ describe('parentSampleRate propagation with tracesSampler', () => {
1111
expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.69/);
1212
});
1313

14-
test('should propagate sample_rate equivalent to sample rate returned by tracesSampler when there is no incoming sample rate', async () => {
14+
test('should propagate sample_rate equivalent to sample rate returned by tracesSampler when there is no incoming sample rate (1 -> because there is a positive sampling decision and inheritOrSampleWith was used)', async () => {
1515
const runner = createRunner(__dirname, 'server.js').start();
1616
const response = await runner.makeRequest('get', '/check', {
1717
headers: {
@@ -20,6 +20,30 @@ describe('parentSampleRate propagation with tracesSampler', () => {
2020
},
2121
});
2222

23+
expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=1/);
24+
});
25+
26+
test('should propagate sample_rate equivalent to sample rate returned by tracesSampler when there is no incoming sample rate (0 -> because there is a negative sampling decision and inheritOrSampleWith was used)', async () => {
27+
const runner = createRunner(__dirname, 'server.js').start();
28+
const response = await runner.makeRequest('get', '/check', {
29+
headers: {
30+
'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac-0',
31+
baggage: '',
32+
},
33+
});
34+
35+
expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0/);
36+
});
37+
38+
test('should propagate sample_rate equivalent to sample rate returned by tracesSampler when there is no incoming sample rate (the fallback value -> because there is no sampling decision and inheritOrSampleWith was used)', async () => {
39+
const runner = createRunner(__dirname, 'server.js').start();
40+
const response = await runner.makeRequest('get', '/check', {
41+
headers: {
42+
'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac',
43+
baggage: '',
44+
},
45+
});
46+
2347
expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.69/);
2448
});
2549

packages/core/src/tracing/sampling.ts

+18-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,24 @@ export function sampleSpan(
2727
// work; prefer the hook if so
2828
let sampleRate;
2929
if (typeof options.tracesSampler === 'function') {
30-
sampleRate = options.tracesSampler(samplingContext);
30+
sampleRate = options.tracesSampler({
31+
...samplingContext,
32+
inheritOrSampleWith: fallbackSampleRate => {
33+
// If we have an incoming parent sample rate, we'll just use that one.
34+
// The sampling decision will be inherited because of the sample_rand that was generated when the trace reached the incoming boundaries of the SDK.
35+
if (typeof samplingContext.parentSampleRate === 'number') {
36+
return samplingContext.parentSampleRate;
37+
}
38+
39+
// Fallback if parent sample rate is not on the incoming trace (e.g. if there is no baggage)
40+
// This is to provide backwards compatibility if there are incoming traces from older SDKs that don't send a parent sample rate or a sample rand. In these cases we just want to force either a sampling decision on the downstream traces via the sample rate.
41+
if (typeof samplingContext.parentSampled === 'boolean') {
42+
return Number(samplingContext.parentSampled);
43+
}
44+
45+
return fallbackSampleRate;
46+
},
47+
});
3148
localSampleRateWasApplied = true;
3249
} else if (samplingContext.parentSampled !== undefined) {
3350
sampleRate = samplingContext.parentSampled;

packages/core/src/types-hoist/options.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import type { CaptureContext } from '../scope';
22
import type { Breadcrumb, BreadcrumbHint } from './breadcrumb';
33
import type { ErrorEvent, EventHint, TransactionEvent } from './event';
44
import type { Integration } from './integration';
5-
import type { SamplingContext } from './samplingcontext';
5+
import type { TracesSamplerSamplingContext } from './samplingcontext';
66
import type { SdkMetadata } from './sdkmetadata';
77
import type { SpanJSON } from './span';
88
import type { StackLineParser, StackParser } from './stacktrace';
@@ -243,7 +243,7 @@ export interface ClientOptions<TO extends BaseTransportOptions = BaseTransportOp
243243
* @returns A sample rate between 0 and 1 (0 drops the trace, 1 guarantees it will be sent). Returning `true` is
244244
* equivalent to returning 1 and returning `false` is equivalent to returning 0.
245245
*/
246-
tracesSampler?: (samplingContext: SamplingContext) => number | boolean;
246+
tracesSampler?: (samplingContext: TracesSamplerSamplingContext) => number | boolean;
247247

248248
/**
249249
* An event-processing callback for error and message events, guaranteed to be invoked after all other event

packages/core/src/types-hoist/samplingcontext.ts

+11-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,7 @@ export interface CustomSamplingContext {
1010
}
1111

1212
/**
13-
* Data passed to the `tracesSampler` function, which forms the basis for whatever decisions it might make.
14-
*
15-
* Adds default data to data provided by the user.
13+
* Auxiliary data for various sampling mechanisms in the Sentry SDK.
1614
*/
1715
export interface SamplingContext extends CustomSamplingContext {
1816
/**
@@ -42,3 +40,13 @@ export interface SamplingContext extends CustomSamplingContext {
4240
/** Initial attributes that have been passed to the span being sampled. */
4341
attributes?: SpanAttributes;
4442
}
43+
44+
/**
45+
* Auxiliary data passed to the `tracesSampler` function.
46+
*/
47+
export interface TracesSamplerSamplingContext extends SamplingContext {
48+
/**
49+
* Returns a sample rate value that matches the sampling decision from the incoming trace, or falls back to the provided `fallbackSampleRate`.
50+
*/
51+
inheritOrSampleWith: (fallbackSampleRate: number) => number;
52+
}

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

+1
Original file line numberDiff line numberDiff line change
@@ -608,6 +608,7 @@ describe('startSpan', () => {
608608
test2: 'aa',
609609
test3: 'bb',
610610
},
611+
inheritOrSampleWith: expect.any(Function),
611612
});
612613
});
613614

packages/opentelemetry/test/trace.test.ts

+4
Original file line numberDiff line numberDiff line change
@@ -1350,6 +1350,7 @@ describe('trace (sampling)', () => {
13501350
parentSampled: undefined,
13511351
name: 'outer',
13521352
attributes: {},
1353+
inheritOrSampleWith: expect.any(Function),
13531354
});
13541355

13551356
// Now return `false`, it should not sample
@@ -1416,6 +1417,7 @@ describe('trace (sampling)', () => {
14161417
attr2: 1,
14171418
'sentry.op': 'test.op',
14181419
},
1420+
inheritOrSampleWith: expect.any(Function),
14191421
});
14201422

14211423
// Now return `0`, it should not sample
@@ -1457,6 +1459,7 @@ describe('trace (sampling)', () => {
14571459
parentSampled: undefined,
14581460
name: 'outer3',
14591461
attributes: {},
1462+
inheritOrSampleWith: expect.any(Function),
14601463
});
14611464
});
14621465

@@ -1490,6 +1493,7 @@ describe('trace (sampling)', () => {
14901493
parentSampled: true,
14911494
name: 'outer',
14921495
attributes: {},
1496+
inheritOrSampleWith: expect.any(Function),
14931497
});
14941498
});
14951499

0 commit comments

Comments
 (0)