Skip to content

Commit

Permalink
feat(core): Add inheritOrSampleWith helper to traceSampler (#15277)
Browse files Browse the repository at this point in the history
  • Loading branch information
lforst authored Feb 4, 2025
1 parent 76854e8 commit a3e08ad
Show file tree
Hide file tree
Showing 7 changed files with 63 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,8 @@ const Sentry = require('@sentry/node');
Sentry.init({
dsn: 'https://[email protected]/1337',
transport: loggingTransport,
tracesSampler: ({ parentSampleRate }) => {
if (parentSampleRate) {
return parentSampleRate;
}

return 0.69;
tracesSampler: ({ inheritOrSampleWith }) => {
return inheritOrSampleWith(0.69);
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ describe('parentSampleRate propagation with tracesSampler', () => {
expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.69/);
});

test('should propagate sample_rate equivalent to sample rate returned by tracesSampler when there is no incoming sample rate', async () => {
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 () => {
const runner = createRunner(__dirname, 'server.js').start();
const response = await runner.makeRequest('get', '/check', {
headers: {
Expand All @@ -20,6 +20,30 @@ describe('parentSampleRate propagation with tracesSampler', () => {
},
});

expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=1/);
});

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 () => {
const runner = createRunner(__dirname, 'server.js').start();
const response = await runner.makeRequest('get', '/check', {
headers: {
'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac-0',
baggage: '',
},
});

expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0/);
});

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 () => {
const runner = createRunner(__dirname, 'server.js').start();
const response = await runner.makeRequest('get', '/check', {
headers: {
'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac',
baggage: '',
},
});

expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.69/);
});

Expand Down
19 changes: 18 additions & 1 deletion packages/core/src/tracing/sampling.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,24 @@ export function sampleSpan(
// work; prefer the hook if so
let sampleRate;
if (typeof options.tracesSampler === 'function') {
sampleRate = options.tracesSampler(samplingContext);
sampleRate = options.tracesSampler({
...samplingContext,
inheritOrSampleWith: fallbackSampleRate => {
// If we have an incoming parent sample rate, we'll just use that one.
// The sampling decision will be inherited because of the sample_rand that was generated when the trace reached the incoming boundaries of the SDK.
if (typeof samplingContext.parentSampleRate === 'number') {
return samplingContext.parentSampleRate;
}

// Fallback if parent sample rate is not on the incoming trace (e.g. if there is no baggage)
// 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.
if (typeof samplingContext.parentSampled === 'boolean') {
return Number(samplingContext.parentSampled);
}

return fallbackSampleRate;
},
});
localSampleRateWasApplied = true;
} else if (samplingContext.parentSampled !== undefined) {
sampleRate = samplingContext.parentSampled;
Expand Down
4 changes: 2 additions & 2 deletions packages/core/src/types-hoist/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { CaptureContext } from '../scope';
import type { Breadcrumb, BreadcrumbHint } from './breadcrumb';
import type { ErrorEvent, EventHint, TransactionEvent } from './event';
import type { Integration } from './integration';
import type { SamplingContext } from './samplingcontext';
import type { TracesSamplerSamplingContext } from './samplingcontext';
import type { SdkMetadata } from './sdkmetadata';
import type { SpanJSON } from './span';
import type { StackLineParser, StackParser } from './stacktrace';
Expand Down Expand Up @@ -243,7 +243,7 @@ export interface ClientOptions<TO extends BaseTransportOptions = BaseTransportOp
* @returns A sample rate between 0 and 1 (0 drops the trace, 1 guarantees it will be sent). Returning `true` is
* equivalent to returning 1 and returning `false` is equivalent to returning 0.
*/
tracesSampler?: (samplingContext: SamplingContext) => number | boolean;
tracesSampler?: (samplingContext: TracesSamplerSamplingContext) => number | boolean;

/**
* An event-processing callback for error and message events, guaranteed to be invoked after all other event
Expand Down
14 changes: 11 additions & 3 deletions packages/core/src/types-hoist/samplingcontext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,7 @@ export interface CustomSamplingContext {
}

/**
* Data passed to the `tracesSampler` function, which forms the basis for whatever decisions it might make.
*
* Adds default data to data provided by the user.
* Auxiliary data for various sampling mechanisms in the Sentry SDK.
*/
export interface SamplingContext extends CustomSamplingContext {
/**
Expand Down Expand Up @@ -42,3 +40,13 @@ export interface SamplingContext extends CustomSamplingContext {
/** Initial attributes that have been passed to the span being sampled. */
attributes?: SpanAttributes;
}

/**
* Auxiliary data passed to the `tracesSampler` function.
*/
export interface TracesSamplerSamplingContext extends SamplingContext {
/**
* Returns a sample rate value that matches the sampling decision from the incoming trace, or falls back to the provided `fallbackSampleRate`.
*/
inheritOrSampleWith: (fallbackSampleRate: number) => number;
}
1 change: 1 addition & 0 deletions packages/core/test/lib/tracing/trace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,7 @@ describe('startSpan', () => {
test2: 'aa',
test3: 'bb',
},
inheritOrSampleWith: expect.any(Function),
});
});

Expand Down
4 changes: 4 additions & 0 deletions packages/opentelemetry/test/trace.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1350,6 +1350,7 @@ describe('trace (sampling)', () => {
parentSampled: undefined,
name: 'outer',
attributes: {},
inheritOrSampleWith: expect.any(Function),
});

// Now return `false`, it should not sample
Expand Down Expand Up @@ -1416,6 +1417,7 @@ describe('trace (sampling)', () => {
attr2: 1,
'sentry.op': 'test.op',
},
inheritOrSampleWith: expect.any(Function),
});

// Now return `0`, it should not sample
Expand Down Expand Up @@ -1457,6 +1459,7 @@ describe('trace (sampling)', () => {
parentSampled: undefined,
name: 'outer3',
attributes: {},
inheritOrSampleWith: expect.any(Function),
});
});

Expand Down Expand Up @@ -1490,6 +1493,7 @@ describe('trace (sampling)', () => {
parentSampled: true,
name: 'outer',
attributes: {},
inheritOrSampleWith: expect.any(Function),
});
});

Expand Down

0 comments on commit a3e08ad

Please sign in to comment.