UNPKG

@sentry/core

Version:
244 lines (221 loc) 8.76 kB
import { logger, isNaN } from '@sentry/utils'; import { getMainCarrier } from '../hub.js'; import { hasTracingEnabled } from '../utils/hasTracingEnabled.js'; import { registerErrorInstrumentation } from './errors.js'; import { IdleTransaction } from './idletransaction.js'; import { Transaction } from './transaction.js'; /** Returns all trace headers that are currently on the top scope. */ function traceHeaders() { const scope = this.getScope(); if (scope) { const span = scope.getSpan(); if (span) { return { 'sentry-trace': span.toTraceparent(), }; } } return {}; } /** * Makes a sampling decision for the given transaction and stores it on the transaction. * * Called every time a transaction is created. Only transactions which emerge with a `sampled` value of `true` will be * sent to Sentry. * * @param transaction: The transaction needing a sampling decision * @param options: The current client's options, so we can access `tracesSampleRate` and/or `tracesSampler` * @param samplingContext: Default and user-provided data which may be used to help make the decision * * @returns The given transaction with its `sampled` value set */ function sample( transaction, options, samplingContext, ) { // nothing to do if tracing is not enabled if (!hasTracingEnabled(options)) { transaction.sampled = false; return transaction; } // if the user has forced a sampling decision by passing a `sampled` value in their transaction context, go with that if (transaction.sampled !== undefined) { transaction.setMetadata({ sampleRate: Number(transaction.sampled), }); return transaction; } // we would have bailed already if neither `tracesSampler` nor `tracesSampleRate` nor `enableTracing` were defined, so one of these should // work; prefer the hook if so let sampleRate; if (typeof options.tracesSampler === 'function') { sampleRate = options.tracesSampler(samplingContext); transaction.setMetadata({ sampleRate: Number(sampleRate), }); } else if (samplingContext.parentSampled !== undefined) { sampleRate = samplingContext.parentSampled; } else if (typeof options.tracesSampleRate !== 'undefined') { sampleRate = options.tracesSampleRate; transaction.setMetadata({ sampleRate: Number(sampleRate), }); } else { // When `enableTracing === true`, we use a sample rate of 100% sampleRate = 1; transaction.setMetadata({ sampleRate, }); } // Since this is coming from the user (or from a function provided by the user), who knows what we might get. (The // only valid values are booleans or numbers between 0 and 1.) if (!isValidSampleRate(sampleRate)) { (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn('[Tracing] Discarding transaction because of invalid sample rate.'); transaction.sampled = false; return transaction; } // if the function returned 0 (or false), or if `tracesSampleRate` is 0, it's a sign the transaction should be dropped if (!sampleRate) { (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log( `[Tracing] Discarding transaction because ${ typeof options.tracesSampler === 'function' ? 'tracesSampler returned 0 or false' : 'a negative sampling decision was inherited or tracesSampleRate is set to 0' }`, ); transaction.sampled = false; return transaction; } // Now we roll the dice. Math.random is inclusive of 0, but not of 1, so strict < is safe here. In case sampleRate is // a boolean, the < comparison will cause it to be automatically cast to 1 if it's true and 0 if it's false. transaction.sampled = Math.random() < (sampleRate ); // if we're not going to keep it, we're done if (!transaction.sampled) { (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log( `[Tracing] Discarding transaction because it's not included in the random sample (sampling rate = ${Number( sampleRate, )})`, ); return transaction; } (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log(`[Tracing] starting ${transaction.op} transaction - ${transaction.name}`); return transaction; } /** * Checks the given sample rate to make sure it is valid type and value (a boolean, or a number between 0 and 1). */ function isValidSampleRate(rate) { // we need to check NaN explicitly because it's of type 'number' and therefore wouldn't get caught by this typecheck // eslint-disable-next-line @typescript-eslint/no-explicit-any if (isNaN(rate) || !(typeof rate === 'number' || typeof rate === 'boolean')) { (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn( `[Tracing] Given sample rate is invalid. Sample rate must be a boolean or a number between 0 and 1. Got ${JSON.stringify( rate, )} of type ${JSON.stringify(typeof rate)}.`, ); return false; } // in case sampleRate is a boolean, it will get automatically cast to 1 if it's true and 0 if it's false if (rate < 0 || rate > 1) { (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn(`[Tracing] Given sample rate is invalid. Sample rate must be between 0 and 1. Got ${rate}.`); return false; } return true; } /** * Creates a new transaction and adds a sampling decision if it doesn't yet have one. * * The Hub.startTransaction method delegates to this method to do its work, passing the Hub instance in as `this`, as if * it had been called on the hub directly. Exists as a separate function so that it can be injected into the class as an * "extension method." * * @param this: The Hub starting the transaction * @param transactionContext: Data used to configure the transaction * @param CustomSamplingContext: Optional data to be provided to the `tracesSampler` function (if any) * * @returns The new transaction * * @see {@link Hub.startTransaction} */ function _startTransaction( transactionContext, customSamplingContext, ) { const client = this.getClient(); const options = (client && client.getOptions()) || {}; const configInstrumenter = options.instrumenter || 'sentry'; const transactionInstrumenter = transactionContext.instrumenter || 'sentry'; if (configInstrumenter !== transactionInstrumenter) { (typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.error( `A transaction was started with instrumenter=\`${transactionInstrumenter}\`, but the SDK is configured with the \`${configInstrumenter}\` instrumenter. The transaction will not be sampled. Please use the ${configInstrumenter} instrumentation to start transactions.`, ); transactionContext.sampled = false; } let transaction = new Transaction(transactionContext, this); transaction = sample(transaction, options, { parentSampled: transactionContext.parentSampled, transactionContext, ...customSamplingContext, }); if (transaction.sampled) { transaction.initSpanRecorder(options._experiments && (options._experiments.maxSpans )); } if (client && client.emit) { client.emit('startTransaction', transaction); } return transaction; } /** * Create new idle transaction. */ function startIdleTransaction( hub, transactionContext, idleTimeout, finalTimeout, onScope, customSamplingContext, heartbeatInterval, ) { const client = hub.getClient(); const options = (client && client.getOptions()) || {}; let transaction = new IdleTransaction(transactionContext, hub, idleTimeout, finalTimeout, heartbeatInterval, onScope); transaction = sample(transaction, options, { parentSampled: transactionContext.parentSampled, transactionContext, ...customSamplingContext, }); if (transaction.sampled) { transaction.initSpanRecorder(options._experiments && (options._experiments.maxSpans )); } if (client && client.emit) { client.emit('startTransaction', transaction); } return transaction; } /** * Adds tracing extensions to the global hub. */ function addTracingExtensions() { const carrier = getMainCarrier(); if (!carrier.__SENTRY__) { return; } carrier.__SENTRY__.extensions = carrier.__SENTRY__.extensions || {}; if (!carrier.__SENTRY__.extensions.startTransaction) { carrier.__SENTRY__.extensions.startTransaction = _startTransaction; } if (!carrier.__SENTRY__.extensions.traceHeaders) { carrier.__SENTRY__.extensions.traceHeaders = traceHeaders; } registerErrorInstrumentation(); } export { addTracingExtensions, startIdleTransaction }; //# sourceMappingURL=hubextensions.js.map