Skip to content

Commit 9fcee38

Browse files
committed
feat(core): Extract sampleTransaction method out
1 parent c356073 commit 9fcee38

File tree

3 files changed

+128
-125
lines changed

3 files changed

+128
-125
lines changed

packages/core/src/tracing/hubextensions.ts

Lines changed: 5 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import type { ClientOptions, CustomSamplingContext, Options, SamplingContext, TransactionContext } from '@sentry/types';
2-
import { isNaN, logger } from '@sentry/utils';
1+
import type { ClientOptions, CustomSamplingContext, TransactionContext } from '@sentry/types';
2+
import { logger } from '@sentry/utils';
33

44
import type { Hub } from '../hub';
55
import { getMainCarrier } from '../hub';
6-
import { hasTracingEnabled } from '../utils/hasTracingEnabled';
76
import { registerErrorInstrumentation } from './errors';
87
import { IdleTransaction } from './idletransaction';
8+
import { sampleTransaction } from './sampling';
99
import { Transaction } from './transaction';
1010

1111
/** Returns all trace headers that are currently on the top scope. */
@@ -20,126 +20,6 @@ function traceHeaders(this: Hub): { [key: string]: string } {
2020
: {};
2121
}
2222

23-
/**
24-
* Makes a sampling decision for the given transaction and stores it on the transaction.
25-
*
26-
* Called every time a transaction is created. Only transactions which emerge with a `sampled` value of `true` will be
27-
* sent to Sentry.
28-
*
29-
* @param transaction: The transaction needing a sampling decision
30-
* @param options: The current client's options, so we can access `tracesSampleRate` and/or `tracesSampler`
31-
* @param samplingContext: Default and user-provided data which may be used to help make the decision
32-
*
33-
* @returns The given transaction with its `sampled` value set
34-
*/
35-
function sample<T extends Transaction>(
36-
transaction: T,
37-
options: Pick<Options, 'tracesSampleRate' | 'tracesSampler' | 'enableTracing'>,
38-
samplingContext: SamplingContext,
39-
): T {
40-
// nothing to do if tracing is not enabled
41-
if (!hasTracingEnabled(options)) {
42-
transaction.sampled = false;
43-
return transaction;
44-
}
45-
46-
// if the user has forced a sampling decision by passing a `sampled` value in their transaction context, go with that
47-
if (transaction.sampled !== undefined) {
48-
transaction.setMetadata({
49-
sampleRate: Number(transaction.sampled),
50-
});
51-
return transaction;
52-
}
53-
54-
// we would have bailed already if neither `tracesSampler` nor `tracesSampleRate` nor `enableTracing` were defined, so one of these should
55-
// work; prefer the hook if so
56-
let sampleRate;
57-
if (typeof options.tracesSampler === 'function') {
58-
sampleRate = options.tracesSampler(samplingContext);
59-
transaction.setMetadata({
60-
sampleRate: Number(sampleRate),
61-
});
62-
} else if (samplingContext.parentSampled !== undefined) {
63-
sampleRate = samplingContext.parentSampled;
64-
} else if (typeof options.tracesSampleRate !== 'undefined') {
65-
sampleRate = options.tracesSampleRate;
66-
transaction.setMetadata({
67-
sampleRate: Number(sampleRate),
68-
});
69-
} else {
70-
// When `enableTracing === true`, we use a sample rate of 100%
71-
sampleRate = 1;
72-
transaction.setMetadata({
73-
sampleRate,
74-
});
75-
}
76-
77-
// Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The
78-
// only valid values are booleans or numbers between 0 and 1.)
79-
if (!isValidSampleRate(sampleRate)) {
80-
__DEBUG_BUILD__ && logger.warn('[Tracing] Discarding transaction because of invalid sample rate.');
81-
transaction.sampled = false;
82-
return transaction;
83-
}
84-
85-
// if the function returned 0 (or false), or if `tracesSampleRate` is 0, it's a sign the transaction should be dropped
86-
if (!sampleRate) {
87-
__DEBUG_BUILD__ &&
88-
logger.log(
89-
`[Tracing] Discarding transaction because ${
90-
typeof options.tracesSampler === 'function'
91-
? 'tracesSampler returned 0 or false'
92-
: 'a negative sampling decision was inherited or tracesSampleRate is set to 0'
93-
}`,
94-
);
95-
transaction.sampled = false;
96-
return transaction;
97-
}
98-
99-
// Now we roll the dice. Math.random is inclusive of 0, but not of 1, so strict < is safe here. In case sampleRate is
100-
// a boolean, the < comparison will cause it to be automatically cast to 1 if it's true and 0 if it's false.
101-
transaction.sampled = Math.random() < (sampleRate as number | boolean);
102-
103-
// if we're not going to keep it, we're done
104-
if (!transaction.sampled) {
105-
__DEBUG_BUILD__ &&
106-
logger.log(
107-
`[Tracing] Discarding transaction because it's not included in the random sample (sampling rate = ${Number(
108-
sampleRate,
109-
)})`,
110-
);
111-
return transaction;
112-
}
113-
114-
__DEBUG_BUILD__ && logger.log(`[Tracing] starting ${transaction.op} transaction - ${transaction.name}`);
115-
return transaction;
116-
}
117-
118-
/**
119-
* Checks the given sample rate to make sure it is valid type and value (a boolean, or a number between 0 and 1).
120-
*/
121-
function isValidSampleRate(rate: unknown): boolean {
122-
// we need to check NaN explicitly because it's of type 'number' and therefore wouldn't get caught by this typecheck
123-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
124-
if (isNaN(rate) || !(typeof rate === 'number' || typeof rate === 'boolean')) {
125-
__DEBUG_BUILD__ &&
126-
logger.warn(
127-
`[Tracing] Given sample rate is invalid. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify(
128-
rate,
129-
)} of type ${JSON.stringify(typeof rate)}.`,
130-
);
131-
return false;
132-
}
133-
134-
// in case sampleRate is a boolean, it will get automatically cast to 1 if it's true and 0 if it's false
135-
if (rate < 0 || rate > 1) {
136-
__DEBUG_BUILD__ &&
137-
logger.warn(`[Tracing] Given sample rate is invalid. Sample rate must be between 0 and 1. Got ${rate}.`);
138-
return false;
139-
}
140-
return true;
141-
}
142-
14323
/**
14424
* Creates a new transaction and adds a sampling decision if it doesn't yet have one.
14525
*
@@ -177,7 +57,7 @@ The transaction will not be sampled. Please use the ${configInstrumenter} instru
17757
}
17858

17959
let transaction = new Transaction(transactionContext, this);
180-
transaction = sample(transaction, options, {
60+
transaction = sampleTransaction(transaction, options, {
18161
parentSampled: transactionContext.parentSampled,
18262
transactionContext,
18363
...customSamplingContext,
@@ -207,7 +87,7 @@ export function startIdleTransaction(
20787
const options: Partial<ClientOptions> = (client && client.getOptions()) || {};
20888

20989
let transaction = new IdleTransaction(transactionContext, hub, idleTimeout, finalTimeout, heartbeatInterval, onScope);
210-
transaction = sample(transaction, options, {
90+
transaction = sampleTransaction(transaction, options, {
21191
parentSampled: transactionContext.parentSampled,
21292
transactionContext,
21393
...customSamplingContext,

packages/core/src/tracing/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ export type { SpanStatusType } from './span';
1111
export { trace, getActiveSpan, startSpan, startInactiveSpan, startActiveSpan, startSpanManual } from './trace';
1212
export { getDynamicSamplingContextFromClient } from './dynamicSamplingContext';
1313
export { setMeasurement } from './measurement';
14+
export { sampleTransaction } from './sampling';

packages/core/src/tracing/sampling.ts

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import type { Options, SamplingContext } from '@sentry/types';
2+
import { isNaN, logger } from '@sentry/utils';
3+
4+
import { hasTracingEnabled } from '../utils/hasTracingEnabled';
5+
import type { Transaction } from './transaction';
6+
7+
/**
8+
* Makes a sampling decision for the given transaction and stores it on the transaction.
9+
*
10+
* Called every time a transaction is created. Only transactions which emerge with a `sampled` value of `true` will be
11+
* sent to Sentry.
12+
*
13+
* This method muttes the given `transaction` and will set the `sampled` value on it.
14+
* It returns the same transaction, for convenience.
15+
*/
16+
export function sampleTransaction<T extends Transaction>(
17+
transaction: T,
18+
options: Pick<Options, 'tracesSampleRate' | 'tracesSampler' | 'enableTracing'>,
19+
samplingContext: SamplingContext,
20+
): T {
21+
// nothing to do if tracing is not enabled
22+
if (!hasTracingEnabled(options)) {
23+
transaction.sampled = false;
24+
return transaction;
25+
}
26+
27+
// if the user has forced a sampling decision by passing a `sampled` value in their transaction context, go with that
28+
if (transaction.sampled !== undefined) {
29+
transaction.setMetadata({
30+
sampleRate: Number(transaction.sampled),
31+
});
32+
return transaction;
33+
}
34+
35+
// we would have bailed already if neither `tracesSampler` nor `tracesSampleRate` nor `enableTracing` were defined, so one of these should
36+
// work; prefer the hook if so
37+
let sampleRate;
38+
if (typeof options.tracesSampler === 'function') {
39+
sampleRate = options.tracesSampler(samplingContext);
40+
transaction.setMetadata({
41+
sampleRate: Number(sampleRate),
42+
});
43+
} else if (samplingContext.parentSampled !== undefined) {
44+
sampleRate = samplingContext.parentSampled;
45+
} else if (typeof options.tracesSampleRate !== 'undefined') {
46+
sampleRate = options.tracesSampleRate;
47+
transaction.setMetadata({
48+
sampleRate: Number(sampleRate),
49+
});
50+
} else {
51+
// When `enableTracing === true`, we use a sample rate of 100%
52+
sampleRate = 1;
53+
transaction.setMetadata({
54+
sampleRate,
55+
});
56+
}
57+
58+
// Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The
59+
// only valid values are booleans or numbers between 0 and 1.)
60+
if (!isValidSampleRate(sampleRate)) {
61+
__DEBUG_BUILD__ && logger.warn('[Tracing] Discarding transaction because of invalid sample rate.');
62+
transaction.sampled = false;
63+
return transaction;
64+
}
65+
66+
// if the function returned 0 (or false), or if `tracesSampleRate` is 0, it's a sign the transaction should be dropped
67+
if (!sampleRate) {
68+
__DEBUG_BUILD__ &&
69+
logger.log(
70+
`[Tracing] Discarding transaction because ${
71+
typeof options.tracesSampler === 'function'
72+
? 'tracesSampler returned 0 or false'
73+
: 'a negative sampling decision was inherited or tracesSampleRate is set to 0'
74+
}`,
75+
);
76+
transaction.sampled = false;
77+
return transaction;
78+
}
79+
80+
// Now we roll the dice. Math.random is inclusive of 0, but not of 1, so strict < is safe here. In case sampleRate is
81+
// a boolean, the < comparison will cause it to be automatically cast to 1 if it's true and 0 if it's false.
82+
transaction.sampled = Math.random() < (sampleRate as number | boolean);
83+
84+
// if we're not going to keep it, we're done
85+
if (!transaction.sampled) {
86+
__DEBUG_BUILD__ &&
87+
logger.log(
88+
`[Tracing] Discarding transaction because it's not included in the random sample (sampling rate = ${Number(
89+
sampleRate,
90+
)})`,
91+
);
92+
return transaction;
93+
}
94+
95+
__DEBUG_BUILD__ && logger.log(`[Tracing] starting ${transaction.op} transaction - ${transaction.name}`);
96+
return transaction;
97+
}
98+
99+
/**
100+
* Checks the given sample rate to make sure it is valid type and value (a boolean, or a number between 0 and 1).
101+
*/
102+
function isValidSampleRate(rate: unknown): boolean {
103+
// we need to check NaN explicitly because it's of type 'number' and therefore wouldn't get caught by this typecheck
104+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
105+
if (isNaN(rate) || !(typeof rate === 'number' || typeof rate === 'boolean')) {
106+
__DEBUG_BUILD__ &&
107+
logger.warn(
108+
`[Tracing] Given sample rate is invalid. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify(
109+
rate,
110+
)} of type ${JSON.stringify(typeof rate)}.`,
111+
);
112+
return false;
113+
}
114+
115+
// in case sampleRate is a boolean, it will get automatically cast to 1 if it's true and 0 if it's false
116+
if (rate < 0 || rate > 1) {
117+
__DEBUG_BUILD__ &&
118+
logger.warn(`[Tracing] Given sample rate is invalid. Sample rate must be between 0 and 1. Got ${rate}.`);
119+
return false;
120+
}
121+
return true;
122+
}

0 commit comments

Comments
 (0)