Files
apps.apple.com/shared/logger/node_modules/@sentry-internal/tracing/esm/browser/browsertracing.js
2025-11-04 05:03:50 +08:00

301 lines
11 KiB
JavaScript

import { TRACING_DEFAULTS, addTracingExtensions, extractTraceparentData, startIdleTransaction, getActiveTransaction } from '@sentry/core';
import { logger, baggageHeaderToDynamicSamplingContext, getDomElement } from '@sentry/utils';
import { registerBackgroundTabDetection } from './backgroundtab.js';
import { startTrackingWebVitals, startTrackingLongTasks, startTrackingInteractions, addPerformanceEntries } from './metrics/index.js';
import { defaultRequestInstrumentationOptions, instrumentOutgoingRequests } from './request.js';
import { instrumentRoutingWithDefaults } from './router.js';
import { WINDOW } from './types.js';
const BROWSER_TRACING_INTEGRATION_ID = 'BrowserTracing';
/** Options for Browser Tracing integration */
const DEFAULT_BROWSER_TRACING_OPTIONS = {
...TRACING_DEFAULTS,
markBackgroundTransactions: true,
routingInstrumentation: instrumentRoutingWithDefaults,
startTransactionOnLocationChange: true,
startTransactionOnPageLoad: true,
enableLongTask: true,
...defaultRequestInstrumentationOptions,
};
/**
* The Browser Tracing integration automatically instruments browser pageload/navigation
* actions as transactions, and captures requests, metrics and errors as spans.
*
* The integration can be configured with a variety of options, and can be extended to use
* any routing library. This integration uses {@see IdleTransaction} to create transactions.
*/
class BrowserTracing {
// This class currently doesn't have a static `id` field like the other integration classes, because it prevented
// @sentry/tracing from being treeshaken. Tree shakers do not like static fields, because they behave like side effects.
// TODO: Come up with a better plan, than using static fields on integration classes, and use that plan on all
// integrations.
/** Browser Tracing integration options */
/**
* @inheritDoc
*/
__init() {this.name = BROWSER_TRACING_INTEGRATION_ID;}
__init2() {this._hasSetTracePropagationTargets = false;}
constructor(_options) {BrowserTracing.prototype.__init.call(this);BrowserTracing.prototype.__init2.call(this);
addTracingExtensions();
if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__)) {
this._hasSetTracePropagationTargets = !!(
_options &&
// eslint-disable-next-line deprecation/deprecation
(_options.tracePropagationTargets || _options.tracingOrigins)
);
}
this.options = {
...DEFAULT_BROWSER_TRACING_OPTIONS,
..._options,
};
// Special case: enableLongTask can be set in _experiments
// TODO (v8): Remove this in v8
if (this.options._experiments.enableLongTask !== undefined) {
this.options.enableLongTask = this.options._experiments.enableLongTask;
}
// TODO (v8): remove this block after tracingOrigins is removed
// Set tracePropagationTargets to tracingOrigins if specified by the user
// In case both are specified, tracePropagationTargets takes precedence
// eslint-disable-next-line deprecation/deprecation
if (_options && !_options.tracePropagationTargets && _options.tracingOrigins) {
// eslint-disable-next-line deprecation/deprecation
this.options.tracePropagationTargets = _options.tracingOrigins;
}
this._collectWebVitals = startTrackingWebVitals();
if (this.options.enableLongTask) {
startTrackingLongTasks();
}
if (this.options._experiments.enableInteractions) {
startTrackingInteractions();
}
}
/**
* @inheritDoc
*/
setupOnce(_, getCurrentHub) {
this._getCurrentHub = getCurrentHub;
const hub = getCurrentHub();
const client = hub.getClient();
const clientOptions = client && client.getOptions();
const {
routingInstrumentation: instrumentRouting,
startTransactionOnLocationChange,
startTransactionOnPageLoad,
markBackgroundTransactions,
traceFetch,
traceXHR,
shouldCreateSpanForRequest,
_experiments,
} = this.options;
const clientOptionsTracePropagationTargets = clientOptions && clientOptions.tracePropagationTargets;
// There are three ways to configure tracePropagationTargets:
// 1. via top level client option `tracePropagationTargets`
// 2. via BrowserTracing option `tracePropagationTargets`
// 3. via BrowserTracing option `tracingOrigins` (deprecated)
//
// To avoid confusion, favour top level client option `tracePropagationTargets`, and fallback to
// BrowserTracing option `tracePropagationTargets` and then `tracingOrigins` (deprecated).
// This is done as it minimizes bundle size (we don't have to have undefined checks).
//
// If both 1 and either one of 2 or 3 are set (from above), we log out a warning.
const tracePropagationTargets = clientOptionsTracePropagationTargets || this.options.tracePropagationTargets;
if ((typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && this._hasSetTracePropagationTargets && clientOptionsTracePropagationTargets) {
logger.warn(
'[Tracing] The `tracePropagationTargets` option was set in the BrowserTracing integration and top level `Sentry.init`. The top level `Sentry.init` value is being used.',
);
}
instrumentRouting(
(context) => {
const transaction = this._createRouteTransaction(context);
this.options._experiments.onStartRouteTransaction &&
this.options._experiments.onStartRouteTransaction(transaction, context, getCurrentHub);
return transaction;
},
startTransactionOnPageLoad,
startTransactionOnLocationChange,
);
if (markBackgroundTransactions) {
registerBackgroundTabDetection();
}
if (_experiments.enableInteractions) {
this._registerInteractionListener();
}
instrumentOutgoingRequests({
traceFetch,
traceXHR,
tracePropagationTargets,
shouldCreateSpanForRequest,
_experiments: {
enableHTTPTimings: _experiments.enableHTTPTimings,
},
});
}
/** Create routing idle transaction. */
_createRouteTransaction(context) {
if (!this._getCurrentHub) {
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
logger.warn(`[Tracing] Did not create ${context.op} transaction because _getCurrentHub is invalid.`);
return undefined;
}
const { beforeNavigate, idleTimeout, finalTimeout, heartbeatInterval } = this.options;
const isPageloadTransaction = context.op === 'pageload';
const sentryTraceMetaTagValue = isPageloadTransaction ? getMetaContent('sentry-trace') : null;
const baggageMetaTagValue = isPageloadTransaction ? getMetaContent('baggage') : null;
const traceParentData = sentryTraceMetaTagValue ? extractTraceparentData(sentryTraceMetaTagValue) : undefined;
const dynamicSamplingContext = baggageMetaTagValue
? baggageHeaderToDynamicSamplingContext(baggageMetaTagValue)
: undefined;
const expandedContext = {
...context,
...traceParentData,
metadata: {
...context.metadata,
dynamicSamplingContext: traceParentData && !dynamicSamplingContext ? {} : dynamicSamplingContext,
},
trimEnd: true,
};
const modifiedContext = typeof beforeNavigate === 'function' ? beforeNavigate(expandedContext) : expandedContext;
// For backwards compatibility reasons, beforeNavigate can return undefined to "drop" the transaction (prevent it
// from being sent to Sentry).
const finalContext = modifiedContext === undefined ? { ...expandedContext, sampled: false } : modifiedContext;
// If `beforeNavigate` set a custom name, record that fact
finalContext.metadata =
finalContext.name !== expandedContext.name
? { ...finalContext.metadata, source: 'custom' }
: finalContext.metadata;
this._latestRouteName = finalContext.name;
this._latestRouteSource = finalContext.metadata && finalContext.metadata.source;
if (finalContext.sampled === false) {
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
logger.log(`[Tracing] Will not send ${finalContext.op} transaction because of beforeNavigate.`);
}
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.log(`[Tracing] Starting ${finalContext.op} transaction on scope`);
const hub = this._getCurrentHub();
const { location } = WINDOW;
const idleTransaction = startIdleTransaction(
hub,
finalContext,
idleTimeout,
finalTimeout,
true,
{ location }, // for use in the tracesSampler
heartbeatInterval,
);
idleTransaction.registerBeforeFinishCallback(transaction => {
this._collectWebVitals();
addPerformanceEntries(transaction);
});
return idleTransaction ;
}
/** Start listener for interaction transactions */
_registerInteractionListener() {
let inflightInteractionTransaction;
const registerInteractionTransaction = () => {
const { idleTimeout, finalTimeout, heartbeatInterval } = this.options;
const op = 'ui.action.click';
const currentTransaction = getActiveTransaction();
if (currentTransaction && currentTransaction.op && ['navigation', 'pageload'].includes(currentTransaction.op)) {
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
logger.warn(
`[Tracing] Did not create ${op} transaction because a pageload or navigation transaction is in progress.`,
);
return undefined;
}
if (inflightInteractionTransaction) {
inflightInteractionTransaction.setFinishReason('interactionInterrupted');
inflightInteractionTransaction.finish();
inflightInteractionTransaction = undefined;
}
if (!this._getCurrentHub) {
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) && logger.warn(`[Tracing] Did not create ${op} transaction because _getCurrentHub is invalid.`);
return undefined;
}
if (!this._latestRouteName) {
(typeof __SENTRY_DEBUG__ === 'undefined' || __SENTRY_DEBUG__) &&
logger.warn(`[Tracing] Did not create ${op} transaction because _latestRouteName is missing.`);
return undefined;
}
const hub = this._getCurrentHub();
const { location } = WINDOW;
const context = {
name: this._latestRouteName,
op,
trimEnd: true,
metadata: {
source: this._latestRouteSource || 'url',
},
};
inflightInteractionTransaction = startIdleTransaction(
hub,
context,
idleTimeout,
finalTimeout,
true,
{ location }, // for use in the tracesSampler
heartbeatInterval,
);
};
['click'].forEach(type => {
addEventListener(type, registerInteractionTransaction, { once: false, capture: true });
});
}
}
/** Returns the value of a meta tag */
function getMetaContent(metaName) {
// Can't specify generic to `getDomElement` because tracing can be used
// in a variety of environments, have to disable `no-unsafe-member-access`
// as a result.
const metaTag = getDomElement(`meta[name=${metaName}]`);
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
return metaTag ? metaTag.getAttribute('content') : null;
}
export { BROWSER_TRACING_INTEGRATION_ID, BrowserTracing, getMetaContent };
//# sourceMappingURL=browsertracing.js.map