![JAR search and dependency download from the Maven repository](/logo.png)
package.build.esm.tracing.trace.js Maven / Gradle / Ivy
import { propagationContextFromHeaders, generatePropagationContext, logger } from '@sentry/utils';
import { getMainCarrier } from '../carrier.js';
import { withScope, getCurrentScope, getIsolationScope, getClient } from '../currentScopes.js';
import { getAsyncContextStrategy } from '../asyncContext/index.js';
import { DEBUG_BUILD } from '../debug-build.js';
import { SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE } from '../semanticAttributes.js';
import { handleCallbackErrors } from '../utils/handleCallbackErrors.js';
import { hasTracingEnabled } from '../utils/hasTracingEnabled.js';
import { _setSpanForScope, _getSpanForScope } from '../utils/spanOnScope.js';
import { spanToJSON, addChildSpanToSpan, spanIsSampled, spanTimeInputToSeconds, getRootSpan } from '../utils/spanUtils.js';
import { getDynamicSamplingContextFromSpan, freezeDscOnSpan } from './dynamicSamplingContext.js';
import { logSpanStart } from './logSpans.js';
import { sampleSpan } from './sampling.js';
import { SentryNonRecordingSpan } from './sentryNonRecordingSpan.js';
import { SentrySpan } from './sentrySpan.js';
import { SPAN_STATUS_ERROR } from './spanstatus.js';
import { setCapturedScopesOnSpan } from './utils.js';
const SUPPRESS_TRACING_KEY = '__SENTRY_SUPPRESS_TRACING__';
/**
* Wraps a function with a transaction/span and finishes the span after the function is done.
* The created span is the active span and will be used as parent by other spans created inside the function
* and can be accessed via `Sentry.getActiveSpan()`, as long as the function is executed while the scope is active.
*
* If you want to create a span that is not set as active, use {@link startInactiveSpan}.
*
* You'll always get a span passed to the callback,
* it may just be a non-recording span if the span is not sampled or if tracing is disabled.
*/
function startSpan(options, callback) {
const acs = getAcs();
if (acs.startSpan) {
return acs.startSpan(options, callback);
}
const spanArguments = parseSentrySpanArguments(options);
const { forceTransaction, parentSpan: customParentSpan } = options;
return withScope(options.scope, () => {
// If `options.parentSpan` is defined, we want to wrap the callback in `withActiveSpan`
const wrapper = getActiveSpanWrapper(customParentSpan);
return wrapper(() => {
const scope = getCurrentScope();
const parentSpan = getParentSpan(scope);
const shouldSkipSpan = options.onlyIfParent && !parentSpan;
const activeSpan = shouldSkipSpan
? new SentryNonRecordingSpan()
: createChildOrRootSpan({
parentSpan,
spanArguments,
forceTransaction,
scope,
});
_setSpanForScope(scope, activeSpan);
return handleCallbackErrors(
() => callback(activeSpan),
() => {
// Only update the span status if it hasn't been changed yet, and the span is not yet finished
const { status } = spanToJSON(activeSpan);
if (activeSpan.isRecording() && (!status || status === 'ok')) {
activeSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
}
},
() => activeSpan.end(),
);
});
});
}
/**
* Similar to `Sentry.startSpan`. Wraps a function with a transaction/span, but does not finish the span
* after the function is done automatically. You'll have to call `span.end()` manually.
*
* The created span is the active span and will be used as parent by other spans created inside the function
* and can be accessed via `Sentry.getActiveSpan()`, as long as the function is executed while the scope is active.
*
* You'll always get a span passed to the callback,
* it may just be a non-recording span if the span is not sampled or if tracing is disabled.
*/
function startSpanManual(options, callback) {
const acs = getAcs();
if (acs.startSpanManual) {
return acs.startSpanManual(options, callback);
}
const spanArguments = parseSentrySpanArguments(options);
const { forceTransaction, parentSpan: customParentSpan } = options;
return withScope(options.scope, () => {
// If `options.parentSpan` is defined, we want to wrap the callback in `withActiveSpan`
const wrapper = getActiveSpanWrapper(customParentSpan);
return wrapper(() => {
const scope = getCurrentScope();
const parentSpan = getParentSpan(scope);
const shouldSkipSpan = options.onlyIfParent && !parentSpan;
const activeSpan = shouldSkipSpan
? new SentryNonRecordingSpan()
: createChildOrRootSpan({
parentSpan,
spanArguments,
forceTransaction,
scope,
});
_setSpanForScope(scope, activeSpan);
function finishAndSetSpan() {
activeSpan.end();
}
return handleCallbackErrors(
() => callback(activeSpan, finishAndSetSpan),
() => {
// Only update the span status if it hasn't been changed yet, and the span is not yet finished
const { status } = spanToJSON(activeSpan);
if (activeSpan.isRecording() && (!status || status === 'ok')) {
activeSpan.setStatus({ code: SPAN_STATUS_ERROR, message: 'internal_error' });
}
},
);
});
});
}
/**
* Creates a span. This span is not set as active, so will not get automatic instrumentation spans
* as children or be able to be accessed via `Sentry.getActiveSpan()`.
*
* If you want to create a span that is set as active, use {@link startSpan}.
*
* This function will always return a span,
* it may just be a non-recording span if the span is not sampled or if tracing is disabled.
*/
function startInactiveSpan(options) {
const acs = getAcs();
if (acs.startInactiveSpan) {
return acs.startInactiveSpan(options);
}
const spanArguments = parseSentrySpanArguments(options);
const { forceTransaction, parentSpan: customParentSpan } = options;
// If `options.scope` is defined, we use this as as a wrapper,
// If `options.parentSpan` is defined, we want to wrap the callback in `withActiveSpan`
const wrapper = options.scope
? (callback) => withScope(options.scope, callback)
: customParentSpan !== undefined
? (callback) => withActiveSpan(customParentSpan, callback)
: (callback) => callback();
return wrapper(() => {
const scope = getCurrentScope();
const parentSpan = getParentSpan(scope);
const shouldSkipSpan = options.onlyIfParent && !parentSpan;
if (shouldSkipSpan) {
return new SentryNonRecordingSpan();
}
return createChildOrRootSpan({
parentSpan,
spanArguments,
forceTransaction,
scope,
});
});
}
/**
* Continue a trace from `sentry-trace` and `baggage` values.
* These values can be obtained from incoming request headers, or in the browser from ``
* and `` HTML tags.
*
* Spans started with `startSpan`, `startSpanManual` and `startInactiveSpan`, within the callback will automatically
* be attached to the incoming trace.
*/
const continueTrace = (
{
sentryTrace,
baggage,
}
,
callback,
) => {
return withScope(scope => {
const propagationContext = propagationContextFromHeaders(sentryTrace, baggage);
scope.setPropagationContext(propagationContext);
return callback();
});
};
/**
* Forks the current scope and sets the provided span as active span in the context of the provided callback. Can be
* passed `null` to start an entirely new span tree.
*
* @param span Spans started in the context of the provided callback will be children of this span. If `null` is passed,
* spans started within the callback will not be attached to a parent span.
* @param callback Execution context in which the provided span will be active. Is passed the newly forked scope.
* @returns the value returned from the provided callback function.
*/
function withActiveSpan(span, callback) {
const acs = getAcs();
if (acs.withActiveSpan) {
return acs.withActiveSpan(span, callback);
}
return withScope(scope => {
_setSpanForScope(scope, span || undefined);
return callback(scope);
});
}
/** Suppress tracing in the given callback, ensuring no spans are generated inside of it. */
function suppressTracing(callback) {
const acs = getAcs();
if (acs.suppressTracing) {
return acs.suppressTracing(callback);
}
return withScope(scope => {
scope.setSDKProcessingMetadata({ [SUPPRESS_TRACING_KEY]: true });
return callback();
});
}
/**
* Starts a new trace for the duration of the provided callback. Spans started within the
* callback will be part of the new trace instead of a potentially previously started trace.
*
* Important: Only use this function if you want to override the default trace lifetime and
* propagation mechanism of the SDK for the duration and scope of the provided callback.
* The newly created trace will also be the root of a new distributed trace, for example if
* you make http requests within the callback.
* This function might be useful if the operation you want to instrument should not be part
* of a potentially ongoing trace.
*
* Default behavior:
* - Server-side: A new trace is started for each incoming request.
* - Browser: A new trace is started for each page our route. Navigating to a new route
* or page will automatically create a new trace.
*/
function startNewTrace(callback) {
return withScope(scope => {
scope.setPropagationContext(generatePropagationContext());
DEBUG_BUILD && logger.info(`Starting a new trace with id ${scope.getPropagationContext().traceId}`);
return withActiveSpan(null, callback);
});
}
function createChildOrRootSpan({
parentSpan,
spanArguments,
forceTransaction,
scope,
}
) {
if (!hasTracingEnabled()) {
return new SentryNonRecordingSpan();
}
const isolationScope = getIsolationScope();
let span;
if (parentSpan && !forceTransaction) {
span = _startChildSpan(parentSpan, scope, spanArguments);
addChildSpanToSpan(parentSpan, span);
} else if (parentSpan) {
// If we forced a transaction but have a parent span, make sure to continue from the parent span, not the scope
const dsc = getDynamicSamplingContextFromSpan(parentSpan);
const { traceId, spanId: parentSpanId } = parentSpan.spanContext();
const parentSampled = spanIsSampled(parentSpan);
span = _startRootSpan(
{
traceId,
parentSpanId,
...spanArguments,
},
scope,
parentSampled,
);
freezeDscOnSpan(span, dsc);
} else {
const {
traceId,
dsc,
parentSpanId,
sampled: parentSampled,
} = {
...isolationScope.getPropagationContext(),
...scope.getPropagationContext(),
};
span = _startRootSpan(
{
traceId,
parentSpanId,
...spanArguments,
},
scope,
parentSampled,
);
if (dsc) {
freezeDscOnSpan(span, dsc);
}
}
logSpanStart(span);
setCapturedScopesOnSpan(span, scope, isolationScope);
return span;
}
/**
* This converts StartSpanOptions to SentrySpanArguments.
* For the most part (for now) we accept the same options,
* but some of them need to be transformed.
*/
function parseSentrySpanArguments(options) {
const exp = options.experimental || {};
const initialCtx = {
isStandalone: exp.standalone,
...options,
};
if (options.startTime) {
const ctx = { ...initialCtx };
ctx.startTimestamp = spanTimeInputToSeconds(options.startTime);
delete ctx.startTime;
return ctx;
}
return initialCtx;
}
function getAcs() {
const carrier = getMainCarrier();
return getAsyncContextStrategy(carrier);
}
function _startRootSpan(spanArguments, scope, parentSampled) {
const client = getClient();
const options = (client && client.getOptions()) || {};
const { name = '', attributes } = spanArguments;
const [sampled, sampleRate] = scope.getScopeData().sdkProcessingMetadata[SUPPRESS_TRACING_KEY]
? [false]
: sampleSpan(options, {
name,
parentSampled,
attributes,
transactionContext: {
name,
parentSampled,
},
});
const rootSpan = new SentrySpan({
...spanArguments,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: 'custom',
...spanArguments.attributes,
},
sampled,
});
if (sampleRate !== undefined) {
rootSpan.setAttribute(SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, sampleRate);
}
if (client) {
client.emit('spanStart', rootSpan);
}
return rootSpan;
}
/**
* Creates a new `Span` while setting the current `Span.id` as `parentSpanId`.
* This inherits the sampling decision from the parent span.
*/
function _startChildSpan(parentSpan, scope, spanArguments) {
const { spanId, traceId } = parentSpan.spanContext();
const sampled = scope.getScopeData().sdkProcessingMetadata[SUPPRESS_TRACING_KEY] ? false : spanIsSampled(parentSpan);
const childSpan = sampled
? new SentrySpan({
...spanArguments,
parentSpanId: spanId,
traceId,
sampled,
})
: new SentryNonRecordingSpan({ traceId });
addChildSpanToSpan(parentSpan, childSpan);
const client = getClient();
if (client) {
client.emit('spanStart', childSpan);
// If it has an endTimestamp, it's already ended
if (spanArguments.endTimestamp) {
client.emit('spanEnd', childSpan);
}
}
return childSpan;
}
function getParentSpan(scope) {
const span = _getSpanForScope(scope) ;
if (!span) {
return undefined;
}
const client = getClient();
const options = client ? client.getOptions() : {};
if (options.parentSpanIsAlwaysRootSpan) {
return getRootSpan(span) ;
}
return span;
}
function getActiveSpanWrapper(parentSpan) {
return parentSpan !== undefined
? (callback) => {
return withActiveSpan(parentSpan, callback);
}
: (callback) => callback();
}
export { continueTrace, startInactiveSpan, startNewTrace, startSpan, startSpanManual, suppressTracing, withActiveSpan };
//# sourceMappingURL=trace.js.map
© 2015 - 2025 Weber Informatics LLC | Privacy Policy