Skip to content

Commit d5c8aa0

Browse files
authored
fix: Create spanRecorder whenever transactions are sampled (#3255)
There were cases in which the sample function would return early without correctly setting up a spanRecorder, causing child spans to be lost.
1 parent d8ecb2b commit d5c8aa0

File tree

2 files changed

+38
-15
lines changed

2 files changed

+38
-15
lines changed

packages/tracing/src/hubextensions.ts

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { getMainCarrier, Hub } from '@sentry/hub';
2-
import { CustomSamplingContext, SamplingContext, TransactionContext, TransactionSamplingMethod } from '@sentry/types';
2+
import {
3+
CustomSamplingContext,
4+
Options,
5+
SamplingContext,
6+
TransactionContext,
7+
TransactionSamplingMethod,
8+
} from '@sentry/types';
39
import { logger } from '@sentry/utils';
410

511
import { registerErrorInstrumentation } from './errors';
@@ -33,12 +39,9 @@ function traceHeaders(this: Hub): { [key: string]: string } {
3339
*
3440
* @returns The given transaction with its `sampled` value set
3541
*/
36-
function sample<T extends Transaction>(hub: Hub, transaction: T, samplingContext: SamplingContext): T {
37-
const client = hub.getClient();
38-
const options = (client && client.getOptions()) || {};
39-
40-
// nothing to do if there's no client or if tracing is disabled
41-
if (!client || !hasTracingEnabled(options)) {
42+
function sample<T extends Transaction>(transaction: T, options: Options, samplingContext: SamplingContext): T {
43+
// nothing to do if tracing is not enabled
44+
if (!hasTracingEnabled(options)) {
4245
transaction.sampled = false;
4346
return transaction;
4447
}
@@ -114,10 +117,6 @@ function sample<T extends Transaction>(hub: Hub, transaction: T, samplingContext
114117
return transaction;
115118
}
116119

117-
// at this point we know we're keeping the transaction, whether because of an inherited decision or because it got
118-
// lucky with the dice roll
119-
transaction.initSpanRecorder(options._experiments?.maxSpans as number);
120-
121120
logger.log(`[Tracing] starting ${transaction.op} transaction - ${transaction.name}`);
122121
return transaction;
123122
}
@@ -165,12 +164,18 @@ function _startTransaction(
165164
transactionContext: TransactionContext,
166165
customSamplingContext?: CustomSamplingContext,
167166
): Transaction {
168-
const transaction = new Transaction(transactionContext, this);
169-
return sample(this, transaction, {
167+
const options = this.getClient()?.getOptions() || {};
168+
169+
let transaction = new Transaction(transactionContext, this);
170+
transaction = sample(transaction, options, {
170171
parentSampled: transactionContext.parentSampled,
171172
transactionContext,
172173
...customSamplingContext,
173174
});
175+
if (transaction.sampled) {
176+
transaction.initSpanRecorder(options._experiments?.maxSpans as number);
177+
}
178+
return transaction;
174179
}
175180

176181
/**
@@ -183,12 +188,18 @@ export function startIdleTransaction(
183188
onScope?: boolean,
184189
customSamplingContext?: CustomSamplingContext,
185190
): IdleTransaction {
186-
const transaction = new IdleTransaction(transactionContext, hub, idleTimeout, onScope);
187-
return sample(hub, transaction, {
191+
const options = hub.getClient()?.getOptions() || {};
192+
193+
let transaction = new IdleTransaction(transactionContext, hub, idleTimeout, onScope);
194+
transaction = sample(transaction, options, {
188195
parentSampled: transactionContext.parentSampled,
189196
transactionContext,
190197
...customSamplingContext,
191198
});
199+
if (transaction.sampled) {
200+
transaction.initSpanRecorder(options._experiments?.maxSpans as number);
201+
}
202+
return transaction;
192203
}
193204

194205
/**

packages/tracing/test/span.test.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,18 @@ describe('Span', () => {
167167
expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext());
168168
});
169169

170+
// See https://github.com/getsentry/sentry-javascript/issues/3254
171+
test('finish a transaction + child span + sampled:true', () => {
172+
const spy = jest.spyOn(hub as any, 'captureEvent') as any;
173+
const transaction = hub.startTransaction({ name: 'test', op: 'parent', sampled: true });
174+
const childSpan = transaction.startChild({ op: 'child' });
175+
childSpan.finish();
176+
transaction.finish();
177+
expect(spy).toHaveBeenCalled();
178+
expect(spy.mock.calls[0][0].spans).toHaveLength(1);
179+
expect(spy.mock.calls[0][0].contexts.trace).toEqual(transaction.getTraceContext());
180+
});
181+
170182
test("finish a child span shouldn't trigger captureEvent", () => {
171183
const spy = jest.spyOn(hub as any, 'captureEvent') as any;
172184
const transaction = hub.startTransaction({ name: 'test' });

0 commit comments

Comments
 (0)