Skip to content

Commit 354dcee

Browse files
authored
fix(core): Remove sampled flag from dynamic sampling context in Tracing without Performance mode (#13753)
Fix a bug in Core that surfaced in Node apps configured for Tracing without Performance. Previously, we'd incorrectly add `sampled: false` flag when creating the DSC from an active span if the application was configured for TwP. This is possible because in TwP, Otel still emits non-recording spans for the incoming requests. Our implementation in Core didn't account for this edge case yet.
1 parent cb7f16e commit 354dcee

File tree

5 files changed

+46
-19
lines changed

5 files changed

+46
-19
lines changed

dev-packages/node-integration-tests/suites/tracing/meta-tags-twp-errors/server.js

+9-12
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,22 @@ const Sentry = require('@sentry/node');
44
Sentry.init({
55
dsn: 'https://[email protected]/1337',
66
transport: loggingTransport,
7-
beforeSend(event) {
8-
event.contexts = {
9-
...event.contexts,
10-
traceData: {
11-
...Sentry.getTraceData(),
12-
metaTags: Sentry.getTraceMetaTags(),
13-
},
14-
};
15-
return event;
16-
},
177
});
188

199
// express must be required after Sentry is initialized
2010
const express = require('express');
2111

2212
const app = express();
2313

24-
app.get('/test', () => {
25-
throw new Error('test error');
14+
app.get('/test', (_req, res) => {
15+
Sentry.withScope(scope => {
16+
scope.setContext('traceData', {
17+
...Sentry.getTraceData(),
18+
metaTags: Sentry.getTraceMetaTags(),
19+
});
20+
Sentry.captureException(new Error('test error 2'));
21+
});
22+
res.status(200).send();
2623
});
2724

2825
Sentry.setupExpressErrorHandler(app);

dev-packages/node-integration-tests/suites/tracing/meta-tags-twp-errors/test.ts

+6-3
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ describe('errors in TwP mode have same trace in trace context and getTraceData()
55
cleanupChildProcesses();
66
});
77

8-
test('in incoming request', async () => {
8+
test('in incoming request', done => {
99
createRunner(__dirname, 'server.js')
1010
.expect({
1111
event: event => {
@@ -17,14 +17,16 @@ describe('errors in TwP mode have same trace in trace context and getTraceData()
1717
const traceData = contexts?.traceData || {};
1818

1919
expect(traceData['sentry-trace']).toEqual(`${trace_id}-${span_id}`);
20+
2021
expect(traceData.baggage).toContain(`sentry-trace_id=${trace_id}`);
22+
expect(traceData.baggage).not.toContain('sentry-sampled=');
2123

2224
expect(traceData.metaTags).toContain(`<meta name="sentry-trace" content="${trace_id}-${span_id}"/>`);
23-
expect(traceData.metaTags).toContain(`sentr y-trace_id=${trace_id}`);
25+
expect(traceData.metaTags).toContain(`sentry-trace_id=${trace_id}`);
2426
expect(traceData.metaTags).not.toContain('sentry-sampled=');
2527
},
2628
})
27-
.start()
29+
.start(done)
2830
.makeRequest('get', '/test');
2931
});
3032

@@ -41,6 +43,7 @@ describe('errors in TwP mode have same trace in trace context and getTraceData()
4143

4244
expect(traceData['sentry-trace']).toEqual(`${trace_id}-${span_id}`);
4345
expect(traceData.baggage).toContain(`sentry-trace_id=${trace_id}`);
46+
expect(traceData.baggage).not.toContain('sentry-sampled=');
4447

4548
expect(traceData.metaTags).toContain(`<meta name="sentry-trace" content="${trace_id}-${span_id}"/>`);
4649
expect(traceData.metaTags).toContain(`sentry-trace_id=${trace_id}`);

dev-packages/node-integration-tests/suites/tracing/meta-tags-twp/test.ts

+1
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,6 @@ describe('getTraceMetaTags', () => {
2727
expect(sentryBaggageContent).toContain('sentry-environment=production');
2828
expect(sentryBaggageContent).toContain('sentry-public_key=public');
2929
expect(sentryBaggageContent).toContain(`sentry-trace_id=${traceId}`);
30+
expect(sentryBaggageContent).not.toContain('sentry-sampled=');
3031
});
3132
});

packages/core/src/tracing/dynamicSamplingContext.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
import { DEFAULT_ENVIRONMENT } from '../constants';
1010
import { getClient } from '../currentScopes';
1111
import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '../semanticAttributes';
12+
import { hasTracingEnabled } from '../utils/hasTracingEnabled';
1213
import { getRootSpan, spanIsSampled, spanToJSON } from '../utils/spanUtils';
1314

1415
/**
@@ -103,7 +104,12 @@ export function getDynamicSamplingContextFromSpan(span: Span): Readonly<Partial<
103104
dsc.transaction = name;
104105
}
105106

106-
dsc.sampled = String(spanIsSampled(rootSpan));
107+
// How can we even land here with hasTracingEnabled() returning false?
108+
// Otel creates a Non-recording span in Tracing Without Performance mode when handling incoming requests
109+
// So we end up with an active span that is not sampled (neither positively nor negatively)
110+
if (hasTracingEnabled()) {
111+
dsc.sampled = String(spanIsSampled(rootSpan));
112+
}
107113

108114
client.emit('createDsc', dsc, rootSpan);
109115

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

+23-3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Span, SpanContextData, TransactionSource } from '@sentry/types';
22
import {
33
SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE,
44
SEMANTIC_ATTRIBUTE_SENTRY_SOURCE,
5+
getClient,
56
setCurrentClient,
67
} from '../../../src';
78
import { SentrySpan, getDynamicSamplingContextFromSpan, startInactiveSpan } from '../../../src/tracing';
@@ -68,7 +69,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
6869
environment: 'production',
6970
sampled: 'true',
7071
sample_rate: '0.56',
71-
trace_id: expect.any(String),
72+
trace_id: expect.stringMatching(/^[a-f0-9]{32}$/),
7273
transaction: 'tx',
7374
});
7475
});
@@ -85,7 +86,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
8586
environment: 'production',
8687
sampled: 'true',
8788
sample_rate: '1',
88-
trace_id: expect.any(String),
89+
trace_id: expect.stringMatching(/^[a-f0-9]{32}$/),
8990
transaction: 'tx',
9091
});
9192
});
@@ -107,7 +108,7 @@ describe('getDynamicSamplingContextFromSpan', () => {
107108
environment: 'production',
108109
sampled: 'true',
109110
sample_rate: '0.56',
110-
trace_id: expect.any(String),
111+
trace_id: expect.stringMatching(/^[a-f0-9]{32}$/),
111112
transaction: 'tx',
112113
});
113114
});
@@ -144,4 +145,23 @@ describe('getDynamicSamplingContextFromSpan', () => {
144145
expect(dsc.transaction).toEqual('tx');
145146
});
146147
});
148+
149+
it("doesn't return the sampled flag in the DSC if in Tracing without Performance mode", () => {
150+
const rootSpan = new SentrySpan({
151+
name: 'tx',
152+
sampled: undefined,
153+
});
154+
155+
// Simulate TwP mode by deleting the tracesSampleRate option set in beforeEach
156+
delete getClient()?.getOptions().tracesSampleRate;
157+
158+
const dynamicSamplingContext = getDynamicSamplingContextFromSpan(rootSpan);
159+
160+
expect(dynamicSamplingContext).toStrictEqual({
161+
release: '1.0.1',
162+
environment: 'production',
163+
trace_id: expect.stringMatching(/^[a-f0-9]{32}$/),
164+
transaction: 'tx',
165+
});
166+
});
147167
});

0 commit comments

Comments
 (0)