UNPKG

8.96 kBJavaScriptView Raw
1Object.defineProperty(exports, '__esModule', { value: true });
2
3const utils = require('@sentry/utils');
4const hub = require('../hub.js');
5const hasTracingEnabled = require('../utils/hasTracingEnabled.js');
6const errors = require('./errors.js');
7const idletransaction = require('./idletransaction.js');
8const transaction = require('./transaction.js');
9
10/** Returns all trace headers that are currently on the top scope. */
11function traceHeaders() {
12 const scope = this.getScope();
13 if (scope) {
14 const span = scope.getSpan();
15 if (span) {
16 return {
17 'sentry-trace': span.toTraceparent(),
18 };
19 }
20 }
21 return {};
22}
23
24/**
25 * Makes a sampling decision for the given transaction and stores it on the transaction.
26 *
27 * Called every time a transaction is created. Only transactions which emerge with a `sampled` value of `true` will be
28 * sent to Sentry.
29 *
30 * @param transaction: The transaction needing a sampling decision
31 * @param options: The current client's options, so we can access `tracesSampleRate` and/or `tracesSampler`
32 * @param samplingContext: Default and user-provided data which may be used to help make the decision
33 *
34 * @returns The given transaction with its `sampled` value set
35 */
36function sample(
37 transaction,
38 options,
39 samplingContext,
40) {
41 // nothing to do if tracing is not enabled
42 if (!hasTracingEnabled.hasTracingEnabled(options)) {
43 transaction.sampled = false;
44 return transaction;
45 }
46
47 // if the user has forced a sampling decision by passing a `sampled` value in their transaction context, go with that
48 if (transaction.sampled !== undefined) {
49 transaction.setMetadata({
50 sampleRate: Number(transaction.sampled),
51 });
52 return transaction;
53 }
54
55 // we would have bailed already if neither `tracesSampler` nor `tracesSampleRate` nor `enableTracing` were defined, so one of these should
56 // work; prefer the hook if so
57 let sampleRate;
58 if (typeof options.tracesSampler === 'function') {
59 sampleRate = options.tracesSampler(samplingContext);
60 transaction.setMetadata({
61 sampleRate: Number(sampleRate),
62 });
63 } else if (samplingContext.parentSampled !== undefined) {
64 sampleRate = samplingContext.parentSampled;
65 } else if (typeof options.tracesSampleRate !== 'undefined') {
66 sampleRate = options.tracesSampleRate;
67 transaction.setMetadata({
68 sampleRate: Number(sampleRate),
69 });
70 } else {
71 // When `enableTracing === true`, we use a sample rate of 100%
72 sampleRate = 1;
73 transaction.setMetadata({
74 sampleRate,
75 });
76 }
77
78 // Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The
79 // only valid values are booleans or numbers between 0 and 1.)
80 if (!isValidSampleRate(sampleRate)) {
81 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.warn('[Tracing] Discarding transaction because of invalid sample rate.');
82 transaction.sampled = false;
83 return transaction;
84 }
85
86 // if the function returned 0 (or false), or if `tracesSampleRate` is 0, it's a sign the transaction should be dropped
87 if (!sampleRate) {
88 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
89 utils.logger.log(
90 `[Tracing] Discarding transaction because ${
91 typeof options.tracesSampler === 'function'
92 ? 'tracesSampler returned 0 or false'
93 : 'a negative sampling decision was inherited or tracesSampleRate is set to 0'
94 }`,
95 );
96 transaction.sampled = false;
97 return transaction;
98 }
99
100 // Now we roll the dice. Math.random is inclusive of 0, but not of 1, so strict < is safe here. In case sampleRate is
101 // a boolean, the < comparison will cause it to be automatically cast to 1 if it's true and 0 if it's false.
102 transaction.sampled = Math.random() < (sampleRate );
103
104 // if we're not going to keep it, we're done
105 if (!transaction.sampled) {
106 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
107 utils.logger.log(
108 `[Tracing] Discarding transaction because it's not included in the random sample (sampling rate = ${Number(
109 sampleRate,
110 )})`,
111 );
112 return transaction;
113 }
114
115 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && utils.logger.log(`[Tracing] starting ${transaction.op} transaction - ${transaction.name}`);
116 return transaction;
117}
118
119/**
120 * Checks the given sample rate to make sure it is valid type and value (a boolean, or a number between 0 and 1).
121 */
122function isValidSampleRate(rate) {
123 // we need to check NaN explicitly because it's of type 'number' and therefore wouldn't get caught by this typecheck
124 // eslint-disable-next-line @typescript-eslint/no-explicit-any
125 if (utils.isNaN(rate) || !(typeof rate === 'number' || typeof rate === 'boolean')) {
126 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
127 utils.logger.warn(
128 `[Tracing] Given sample rate is invalid. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify(
129 rate,
130 )} of type ${JSON.stringify(typeof rate)}.`,
131 );
132 return false;
133 }
134
135 // in case sampleRate is a boolean, it will get automatically cast to 1 if it's true and 0 if it's false
136 if (rate < 0 || rate > 1) {
137 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
138 utils.logger.warn(`[Tracing] Given sample rate is invalid. Sample rate must be between 0 and 1. Got ${rate}.`);
139 return false;
140 }
141 return true;
142}
143
144/**
145 * Creates a new transaction and adds a sampling decision if it doesn't yet have one.
146 *
147 * The Hub.startTransaction method delegates to this method to do its work, passing the Hub instance in as `this`, as if
148 * it had been called on the hub directly. Exists as a separate function so that it can be injected into the class as an
149 * "extension method."
150 *
151 * @param this: The Hub starting the transaction
152 * @param transactionContext: Data used to configure the transaction
153 * @param CustomSamplingContext: Optional data to be provided to the `tracesSampler` function (if any)
154 *
155 * @returns The new transaction
156 *
157 * @see {@link Hub.startTransaction}
158 */
159function _startTransaction(
160
161 transactionContext,
162 customSamplingContext,
163) {
164 const client = this.getClient();
165 const options = (client && client.getOptions()) || {};
166
167 const configInstrumenter = options.instrumenter || 'sentry';
168 const transactionInstrumenter = transactionContext.instrumenter || 'sentry';
169
170 if (configInstrumenter !== transactionInstrumenter) {
171 (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
172 utils.logger.error(
173 `A transaction was started with instrumenter=\`${transactionInstrumenter}\`, but the SDK is configured with the \`${configInstrumenter}\` instrumenter.
174The transaction will not be sampled. Please use the ${configInstrumenter} instrumentation to start transactions.`,
175 );
176
177 transactionContext.sampled = false;
178 }
179
180 let transaction$1 = new transaction.Transaction(transactionContext, this);
181 transaction$1 = sample(transaction$1, options, {
182 parentSampled: transactionContext.parentSampled,
183 transactionContext,
184 ...customSamplingContext,
185 });
186 if (transaction$1.sampled) {
187 transaction$1.initSpanRecorder(options._experiments && (options._experiments.maxSpans ));
188 }
189 if (client && client.emit) {
190 client.emit('startTransaction', transaction$1);
191 }
192 return transaction$1;
193}
194
195/**
196 * Create new idle transaction.
197 */
198function startIdleTransaction(
199 hub,
200 transactionContext,
201 idleTimeout,
202 finalTimeout,
203 onScope,
204 customSamplingContext,
205 heartbeatInterval,
206) {
207 const client = hub.getClient();
208 const options = (client && client.getOptions()) || {};
209
210 let transaction = new idletransaction.IdleTransaction(transactionContext, hub, idleTimeout, finalTimeout, heartbeatInterval, onScope);
211 transaction = sample(transaction, options, {
212 parentSampled: transactionContext.parentSampled,
213 transactionContext,
214 ...customSamplingContext,
215 });
216 if (transaction.sampled) {
217 transaction.initSpanRecorder(options._experiments && (options._experiments.maxSpans ));
218 }
219 if (client && client.emit) {
220 client.emit('startTransaction', transaction);
221 }
222 return transaction;
223}
224
225/**
226 * Adds tracing extensions to the global hub.
227 */
228function addTracingExtensions() {
229 const carrier = hub.getMainCarrier();
230 if (!carrier.__SENTRY__) {
231 return;
232 }
233 carrier.__SENTRY__.extensions = carrier.__SENTRY__.extensions || {};
234 if (!carrier.__SENTRY__.extensions.startTransaction) {
235 carrier.__SENTRY__.extensions.startTransaction = _startTransaction;
236 }
237 if (!carrier.__SENTRY__.extensions.traceHeaders) {
238 carrier.__SENTRY__.extensions.traceHeaders = traceHeaders;
239 }
240
241 errors.registerErrorInstrumentation();
242}
243
244exports.addTracingExtensions = addTracingExtensions;
245exports.startIdleTransaction = startIdleTransaction;
246//# sourceMappingURL=hubextensions.js.map