Skip to content

feat: Pass parentSampleRate to tracesSampler #15024

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 53 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
ebcf7bf
Remove unused function
Jan 9, 2025
a83a511
Merge remote-tracking branch 'origin/develop' into lforst-sample-rand
Jan 13, 2025
1646f0b
Write and use sample rand on propagation context
Jan 13, 2025
7fbba52
Implement propagating sampling decision properly
Jan 13, 2025
0b12615
Inject sample rand
Jan 13, 2025
1eac9af
Merge remote-tracking branch 'origin/develop' into lforst-sample-rand
Jan 13, 2025
38fe823
format
Jan 13, 2025
4fb937f
put onto initial dsc
Jan 14, 2025
6c27931
Add tests for `tracesSampleRate` behavior
Jan 14, 2025
8d6984f
mv
Jan 14, 2025
9b394c3
todo comment
Jan 14, 2025
1d4a641
unit tests
Jan 14, 2025
2d35988
node integration tests
Jan 14, 2025
957bc09
browser integration tests
Jan 14, 2025
e1c531f
Fix test
Jan 14, 2025
1fbbf82
Merge remote-tracking branch 'origin/develop' into lforst-sample-rand
Jan 14, 2025
a1d5dd3
test fix
Jan 15, 2025
83ac885
feat: Pass `parentSampleRate` to `tracesSampler`
Jan 15, 2025
045b41f
always apply root span sample rate to dsc
Jan 15, 2025
52ea3d2
e l a b o r a t e
Jan 15, 2025
631ed1b
Dont reinvent util we already have
Jan 15, 2025
37b6ca2
beep boop actually do what I am supposed to beep boop
Jan 15, 2025
5e3f706
don't leak attribute into actual data
Jan 15, 2025
8c83fb9
Merge branch 'lforst-sample-rand' into lforst-parent-sampling-decision
Jan 15, 2025
c4282b8
.
Jan 15, 2025
4a2a7ae
.
Jan 15, 2025
5b81700
.
Jan 15, 2025
9b2afff
tests
Jan 16, 2025
2c0910a
Merge remote-tracking branch 'origin/develop' into lforst-parent-samp…
Jan 16, 2025
6c6febf
something's off
Jan 16, 2025
4092e09
pain
Jan 16, 2025
72c3a64
Merge remote-tracking branch 'origin/develop' into lforst-parent-samp…
Jan 20, 2025
d835de7
otel side of things
Jan 20, 2025
c96c706
help
Jan 20, 2025
fdf0fa7
.
Jan 20, 2025
e9471eb
bless
Jan 20, 2025
2b491f2
beep boop
Jan 20, 2025
a96e9c6
lint
Jan 20, 2025
063984b
fix and test 0% sample rate
Jan 20, 2025
8607cc8
apply before emit
Jan 20, 2025
51526ec
Merge branch 'develop' into lforst-parent-sampling-decision
Jan 20, 2025
08b6561
fix merge mistake
Jan 20, 2025
bceb9f3
Refactor again
Jan 21, 2025
e2ffc52
lint
Jan 21, 2025
feb43ee
update tests
Jan 21, 2025
2a127b2
.
Jan 21, 2025
70c3fd9
fix crap
Jan 21, 2025
0ca98e3
update tests
Jan 21, 2025
812e750
more tests
Jan 21, 2025
2882ca4
e2e tests
Jan 21, 2025
f5a8220
.
Jan 21, 2025
fbee088
Merge remote-tracking branch 'origin/develop' into lforst-parent-samp…
Jan 21, 2025
f683884
.
Jan 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { cleanupChildProcesses, createRunner } from '../../utils/runner';
import { cleanupChildProcesses, createRunner } from '../../../utils/runner';

describe('sample_rand propagation', () => {
afterAll(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
const Sentry = require('@sentry/node');

Sentry.init({
dsn: 'https://[email protected]/1337',
transport: loggingTransport,
});

// express must be required after Sentry is initialized
const express = require('express');
const cors = require('cors');
const {
startExpressServerAndSendPortToRunner,
getPortAppIsRunningOn,
} = require('@sentry-internal/node-integration-tests');

const app = express();

app.use(cors());

app.get('/check', (req, res) => {
const appPort = getPortAppIsRunningOn(app);
// eslint-disable-next-line no-undef
fetch(`http://localhost:${appPort}/bounce`)
.then(r => r.json())
.then(bounceRes => {
res.json({ propagatedData: bounceRes });
});
});

app.get('/bounce', (req, res) => {
res.json({
baggage: req.headers['baggage'],
});
});

Sentry.setupExpressErrorHandler(app);

startExpressServerAndSendPortToRunner(app);
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { cleanupChildProcesses, createRunner } from '../../../../utils/runner';

describe('parentSampleRate propagation with no tracing enabled', () => {
afterAll(() => {
cleanupChildProcesses();
});

test('should propagate an incoming sample rate', async () => {
const runner = createRunner(__dirname, 'server.js').start();
const response = await runner.makeRequest('get', '/check', {
headers: {
'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac-1',
baggage: 'sentry-sample_rate=0.1337',
},
});

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

test('should not propagate a sample rate for root traces', async () => {
const runner = createRunner(__dirname, 'server.js').start();
const response = await runner.makeRequest('get', '/check');
expect((response as any).propagatedData.baggage).not.toMatch(/sentry-sample_rate/);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
const Sentry = require('@sentry/node');

Sentry.init({
dsn: 'https://[email protected]/1337',
transport: loggingTransport,
tracesSampleRate: 0.69,
});

// express must be required after Sentry is initialized
const express = require('express');
const cors = require('cors');
const {
startExpressServerAndSendPortToRunner,
getPortAppIsRunningOn,
} = require('@sentry-internal/node-integration-tests');

const app = express();

app.use(cors());

app.get('/check', (req, res) => {
const appPort = getPortAppIsRunningOn(app);
// eslint-disable-next-line no-undef
fetch(`http://localhost:${appPort}/bounce`)
.then(r => r.json())
.then(bounceRes => {
res.json({ propagatedData: bounceRes });
});
});

app.get('/bounce', (req, res) => {
res.json({
baggage: req.headers['baggage'],
});
});

Sentry.setupExpressErrorHandler(app);

startExpressServerAndSendPortToRunner(app);
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { cleanupChildProcesses, createRunner } from '../../../../utils/runner';

describe('parentSampleRate propagation with tracesSampleRate', () => {
afterAll(() => {
cleanupChildProcesses();
});

test('should propagate incoming sample rate when inheriting a positive sampling decision', async () => {
const runner = createRunner(__dirname, 'server.js').start();
const response = await runner.makeRequest('get', '/check', {
headers: {
'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac-1',
baggage: 'sentry-sample_rate=0.1337',
},
});

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

test('should propagate incoming sample rate when inheriting a negative sampling decision', async () => {
const runner = createRunner(__dirname, 'server.js').start();
const response = await runner.makeRequest('get', '/check', {
headers: {
'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac-0',
baggage: 'sentry-sample_rate=0.1337',
},
});

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

test('should propagate configured sample rate when receiving a trace without sampling decision and sample rate', 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/);
});

test('should propagate configured sample rate when receiving a trace without sampling decision, but with sample rate', async () => {
const runner = createRunner(__dirname, 'server.js').start();
const response = await runner.makeRequest('get', '/check', {
headers: {
'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac',
baggage: 'sentry-sample_rate=0.1337',
},
});

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

test('should propagate configured sample rate when there is no incoming trace', async () => {
const runner = createRunner(__dirname, 'server.js').start();
const response = await runner.makeRequest('get', '/check');
expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.69/);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const { loggingTransport } = require('@sentry-internal/node-integration-tests');
const Sentry = require('@sentry/node');

Sentry.init({
dsn: 'https://[email protected]/1337',
transport: loggingTransport,
tracesSampler: ({ parentSampleRate }) => {
if (parentSampleRate) {
return parentSampleRate;
}

return 0.69;
},
});

// express must be required after Sentry is initialized
const express = require('express');
const cors = require('cors');
const {
startExpressServerAndSendPortToRunner,
getPortAppIsRunningOn,
} = require('@sentry-internal/node-integration-tests');

const app = express();

app.use(cors());

app.get('/check', (req, res) => {
const appPort = getPortAppIsRunningOn(app);
// eslint-disable-next-line no-undef
fetch(`http://localhost:${appPort}/bounce`)
.then(r => r.json())
.then(bounceRes => {
res.json({ propagatedData: bounceRes });
});
});

app.get('/bounce', (req, res) => {
res.json({
baggage: req.headers['baggage'],
});
});

Sentry.setupExpressErrorHandler(app);

startExpressServerAndSendPortToRunner(app);
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { cleanupChildProcesses, createRunner } from '../../../../utils/runner';

describe('parentSampleRate propagation with tracesSampler', () => {
afterAll(() => {
cleanupChildProcesses();
});

test('should propagate sample_rate equivalent to sample rate returned by tracesSampler when there is no incoming trace', async () => {
const runner = createRunner(__dirname, 'server.js').start();
const response = await runner.makeRequest('get', '/check');
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 () => {
const runner = createRunner(__dirname, 'server.js').start();
const response = await runner.makeRequest('get', '/check', {
headers: {
'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac-1',
baggage: '',
},
});

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

test('should propagate sample_rate equivalent to incoming sample_rate (because tracesSampler is configured that way)', async () => {
const runner = createRunner(__dirname, 'server.js').start();
const response = await runner.makeRequest('get', '/check', {
headers: {
'sentry-trace': '530699e319cc067ce440315d74acb312-414dc2a08d5d1dac-1',
baggage: 'sentry-sample_rate=0.1337',
},
});

expect((response as any).propagatedData.baggage).toMatch(/sentry-sample_rate=0\.1337/);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ global.window.TextDecoder = TextDecoder;
import {
SEMANTIC_ATTRIBUTE_SENTRY_OP,
SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN,
SEMANTIC_ATTRIBUTE_SENTRY_OVERRIDE_TRACE_SAMPLE_RATE,
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
TRACING_DEFAULTS,
Expand Down Expand Up @@ -103,6 +104,7 @@ describe('browserTracingIntegration', () => {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.browser',
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_OVERRIDE_TRACE_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
},
span_id: expect.stringMatching(/[a-f0-9]{16}/),
Expand Down Expand Up @@ -174,6 +176,7 @@ describe('browserTracingIntegration', () => {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.pageload.browser',
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_OVERRIDE_TRACE_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'url',
},
span_id: expect.stringMatching(/[a-f0-9]{16}/),
Expand Down Expand Up @@ -288,6 +291,7 @@ describe('browserTracingIntegration', () => {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'manual',
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_OVERRIDE_TRACE_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
},
span_id: expect.stringMatching(/[a-f0-9]{16}/),
Expand Down Expand Up @@ -323,6 +327,7 @@ describe('browserTracingIntegration', () => {
data: {
[SEMANTIC_ATTRIBUTE_SENTRY_OP]: 'pageload',
[SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]: 'auto.test',
[SEMANTIC_ATTRIBUTE_SENTRY_OVERRIDE_TRACE_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: 1,
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
testy: 'yes',
Expand Down
6 changes: 6 additions & 0 deletions packages/core/src/envelope.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Client } from './client';
import { SEMANTIC_ATTRIBUTE_SENTRY_OVERRIDE_TRACE_SAMPLE_RATE } from './semanticAttributes';
import { getDynamicSamplingContextFromSpan } from './tracing/dynamicSamplingContext';
import type { SentrySpan } from './tracing/sentrySpan';
import type {
Expand Down Expand Up @@ -141,6 +142,11 @@ export function createSpanEnvelope(spans: [SentrySpan, ...SentrySpan[]], client?
const items: SpanItem[] = [];
for (const span of spans) {
const spanJson = convertToSpanJSON(span);

// This attribute is important for internal logic but should not leak into actual data
// eslint-disable-next-line @typescript-eslint/no-dynamic-delete
delete spanJson.data[SEMANTIC_ATTRIBUTE_SENTRY_OVERRIDE_TRACE_SAMPLE_RATE];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm no strong feelings, but can we move this somewhere else than here? E.g. into sentrySpan (where we already delete the name attribute) and/or the span exporter (where we already clear some data that we do not want to send)? These feel like more correct places for this than the envelope, which should rather just send what it gets, IMHO.

Copy link
Contributor Author

@lforst lforst Jan 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put this here because there is no other really generic flow that runs for standalone spans. This is basically one-of logic. We already delete it in sentrySpan, but sentrySpan doesn't run for standalones, neither does the span exporter in browser. Do you have any other concrete ideas?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I could also just delete the line. We also seem to be sending other garbage attributes through standalones.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm, I see 🤔 you decide, it feels a bit "weird" to do this in envelope serialization step, but also OK to me!

Copy link
Contributor Author

@lforst lforst Jan 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know. For the lack of better placement I'll leave it. Removing it wouldn't be great since we'd just end up forgetting to do so. It's super easy to refactor once we flesh out standalone spans.


if (spanJson) {
items.push(createSpanEnvelopeItem(spanJson));
}
Expand Down
5 changes: 5 additions & 0 deletions packages/core/src/semanticAttributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ export const SEMANTIC_ATTRIBUTE_SENTRY_SOURCE = 'sentry.source';
*/
export const SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE = 'sentry.sample_rate';

/**
* Use this attribute on a root span to propagate the spans sample rate downstream as parent sample rate in the DSC, overriding anything that was previously set on the DSC.
*/
export const SEMANTIC_ATTRIBUTE_SENTRY_OVERRIDE_TRACE_SAMPLE_RATE = 'sentry.override_trace_sample_rate';

/**
* Use this attribute to represent the operation of a span.
*/
Expand Down
Loading
Loading