@sentry/core
Version:
Base implementation for all Sentry JavaScript SDKs
399 lines (396 loc) • 14.5 kB
JavaScript
import { getAsyncContextStrategy } from '../asyncContext/index.js';
import { getMainCarrier } from '../carrier.js';
import { getClient, withScope, getCurrentScope, getIsolationScope } from '../currentScopes.js';
import { DEBUG_BUILD } from '../debug-build.js';
import { SEMANTIC_ATTRIBUTE_SENTRY_OP, SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '../semanticAttributes.js';
import { baggageHeaderToDynamicSamplingContext } from '../utils/baggage.js';
import { debug } from '../utils/debug-logger.js';
import { handleCallbackErrors } from '../utils/handleCallbackErrors.js';
import { hasSpansEnabled } from '../utils/hasSpansEnabled.js';
import { shouldIgnoreSpan } from '../utils/should-ignore-span.js';
import { hasSpanStreamingEnabled } from './spans/hasSpanStreamingEnabled.js';
import { parseSampleRate } from '../utils/parseSampleRate.js';
import { generateTraceId } from '../utils/propagationContext.js';
import { safeMathRandom } from '../utils/randomSafeContext.js';
import { _getSpanForScope, _setSpanForScope } from '../utils/spanOnScope.js';
import { spanTimeInputToSeconds, getRootSpan, addChildSpanToSpan, spanIsSampled, spanToJSON } from '../utils/spanUtils.js';
import { shouldContinueTrace, propagationContextFromHeaders } from '../utils/tracing.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__";
function startSpan(options, callback) {
const acs = getAcs();
if (acs.startSpan) {
return acs.startSpan(options, callback);
}
const spanArguments = parseSentrySpanArguments(options);
const { forceTransaction, parentSpan: customParentSpan, scope: customScope } = options;
const customForkedScope = customScope?.clone();
return withScope(customForkedScope, () => {
const wrapper = getActiveSpanWrapper(customParentSpan);
return wrapper(() => {
const scope = getCurrentScope();
const parentSpan = getParentSpan(scope, customParentSpan);
const client = getClient();
const missingRequiredParent = options.onlyIfParent && !parentSpan;
const activeSpan = missingRequiredParent ? new SentryNonRecordingSpan() : createChildOrRootSpan({
parentSpan,
spanArguments,
forceTransaction,
scope
});
if (missingRequiredParent) {
client?.recordDroppedEvent("no_parent_span", "span");
}
if (!_isIgnoredSpan(activeSpan) || !parentSpan) {
_setSpanForScope(scope, activeSpan);
}
return handleCallbackErrors(
() => callback(activeSpan),
() => {
const { status } = spanToJSON(activeSpan);
if (activeSpan.isRecording() && (!status || status === "ok")) {
activeSpan.setStatus({ code: SPAN_STATUS_ERROR, message: "internal_error" });
}
},
() => {
activeSpan.end();
}
);
});
});
}
function startSpanManual(options, callback) {
const acs = getAcs();
if (acs.startSpanManual) {
return acs.startSpanManual(options, callback);
}
const spanArguments = parseSentrySpanArguments(options);
const { forceTransaction, parentSpan: customParentSpan, scope: customScope } = options;
const customForkedScope = customScope?.clone();
return withScope(customForkedScope, () => {
const wrapper = getActiveSpanWrapper(customParentSpan);
return wrapper(() => {
const scope = getCurrentScope();
const parentSpan = getParentSpan(scope, customParentSpan);
const missingRequiredParent = options.onlyIfParent && !parentSpan;
const activeSpan = missingRequiredParent ? new SentryNonRecordingSpan() : createChildOrRootSpan({
parentSpan,
spanArguments,
forceTransaction,
scope
});
if (missingRequiredParent) {
getClient()?.recordDroppedEvent("no_parent_span", "span");
}
if (!_isIgnoredSpan(activeSpan) || !parentSpan) {
_setSpanForScope(scope, activeSpan);
}
return handleCallbackErrors(
// We pass the `finish` function to the callback, so the user can finish the span manually
// this is mainly here for historic purposes because previously, we instructed users to call
// `finish` instead of `span.end()` to also clean up the scope. Nowadays, calling `span.end()`
// or `finish` has the same effect and we simply leave it here to avoid breaking user code.
() => callback(activeSpan, () => activeSpan.end()),
() => {
const { status } = spanToJSON(activeSpan);
if (activeSpan.isRecording() && (!status || status === "ok")) {
activeSpan.setStatus({ code: SPAN_STATUS_ERROR, message: "internal_error" });
}
}
);
});
});
}
function startInactiveSpan(options) {
const acs = getAcs();
if (acs.startInactiveSpan) {
return acs.startInactiveSpan(options);
}
const spanArguments = parseSentrySpanArguments(options);
const { forceTransaction, parentSpan: customParentSpan } = options;
const wrapper = options.scope ? (callback) => withScope(options.scope, callback) : customParentSpan !== void 0 ? (callback) => withActiveSpan(customParentSpan, callback) : (callback) => callback();
return wrapper(() => {
const scope = getCurrentScope();
const parentSpan = getParentSpan(scope, customParentSpan);
const client = getClient();
const missingRequiredParent = options.onlyIfParent && !parentSpan;
if (missingRequiredParent) {
client?.recordDroppedEvent("no_parent_span", "span");
return new SentryNonRecordingSpan();
}
return createChildOrRootSpan({
parentSpan,
spanArguments,
forceTransaction,
scope
});
});
}
const continueTrace = (options, callback) => {
const carrier = getMainCarrier();
const acs = getAsyncContextStrategy(carrier);
if (acs.continueTrace) {
return acs.continueTrace(options, callback);
}
const { sentryTrace, baggage } = options;
const client = getClient();
const incomingDsc = baggageHeaderToDynamicSamplingContext(baggage);
if (client && !shouldContinueTrace(client, incomingDsc?.org_id)) {
return startNewTrace(callback);
}
return withScope((scope) => {
const propagationContext = propagationContextFromHeaders(sentryTrace, baggage);
scope.setPropagationContext(propagationContext);
_setSpanForScope(scope, void 0);
return callback();
});
};
function withActiveSpan(span, callback) {
const acs = getAcs();
if (acs.withActiveSpan) {
return acs.withActiveSpan(span, callback);
}
return withScope((scope) => {
_setSpanForScope(scope, span || void 0);
return callback(scope);
});
}
function suppressTracing(callback) {
const acs = getAcs();
if (acs.suppressTracing) {
return acs.suppressTracing(callback);
}
return withScope((scope) => {
scope.setSDKProcessingMetadata({ [SUPPRESS_TRACING_KEY]: true });
const res = callback();
scope.setSDKProcessingMetadata({ [SUPPRESS_TRACING_KEY]: void 0 });
return res;
});
}
function startNewTrace(callback) {
const acs = getAcs();
if (acs.startNewTrace) {
return acs.startNewTrace(callback);
}
return withScope((scope) => {
scope.setPropagationContext({
traceId: generateTraceId(),
sampleRand: safeMathRandom()
});
DEBUG_BUILD && debug.log(`Starting a new trace with id ${scope.getPropagationContext().traceId}`);
return withActiveSpan(null, callback);
});
}
function createChildOrRootSpan({
parentSpan,
spanArguments,
forceTransaction,
scope
}) {
if (!hasSpansEnabled()) {
const span2 = new SentryNonRecordingSpan();
if (forceTransaction || !parentSpan) {
const dsc = {
sampled: "false",
sample_rate: "0",
transaction: spanArguments.name,
...getDynamicSamplingContextFromSpan(span2)
};
freezeDscOnSpan(span2, dsc);
}
return span2;
}
const client = getClient();
if (_shouldIgnoreStreamedSpan(client, spanArguments)) {
if (!_isTracingSuppressed(scope)) {
client?.recordDroppedEvent("ignored", "span");
}
return new SentryNonRecordingSpan({
dropReason: "ignored",
traceId: parentSpan?.spanContext().traceId ?? scope.getPropagationContext().traceId
});
}
const isolationScope = getIsolationScope();
let span;
if (parentSpan && !forceTransaction) {
span = _startChildSpan(parentSpan, scope, spanArguments);
addChildSpanToSpan(parentSpan, span);
} else if (parentSpan) {
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;
}
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?.getOptions() || {};
const { name = "" } = spanArguments;
const mutableSpanSamplingData = { spanAttributes: { ...spanArguments.attributes }, spanName: name, parentSampled };
client?.emit("beforeSampling", mutableSpanSamplingData, { decision: false });
const finalParentSampled = mutableSpanSamplingData.parentSampled ?? parentSampled;
const finalAttributes = mutableSpanSamplingData.spanAttributes;
const currentPropagationContext = scope.getPropagationContext();
const isTracingSuppressed = _isTracingSuppressed(scope);
const [sampled, sampleRate, localSampleRateWasApplied] = isTracingSuppressed ? [false] : sampleSpan(
options,
{
name,
parentSampled: finalParentSampled,
attributes: finalAttributes,
parentSampleRate: parseSampleRate(currentPropagationContext.dsc?.sample_rate)
},
currentPropagationContext.sampleRand
);
const rootSpan = new SentrySpan({
...spanArguments,
attributes: {
[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]: "custom",
[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]: sampleRate !== void 0 && localSampleRateWasApplied ? sampleRate : void 0,
...finalAttributes
},
sampled
});
if (!sampled && client && !isTracingSuppressed) {
DEBUG_BUILD && debug.log("[Tracing] Discarding root span because its trace was not chosen to be sampled.");
client.recordDroppedEvent("sample_rate", hasSpanStreamingEnabled(client) ? "span" : "transaction");
}
if (client) {
client.emit("spanStart", rootSpan);
}
return rootSpan;
}
function _startChildSpan(parentSpan, scope, spanArguments) {
const { spanId, traceId } = parentSpan.spanContext();
const isTracingSuppressed = _isTracingSuppressed(scope);
const sampled = isTracingSuppressed ? false : spanIsSampled(parentSpan);
const childSpan = sampled ? new SentrySpan({
...spanArguments,
parentSpanId: spanId,
traceId,
sampled
}) : new SentryNonRecordingSpan({ traceId });
addChildSpanToSpan(parentSpan, childSpan);
const client = getClient();
if (!client) {
return childSpan;
}
if (hasSpanStreamingEnabled(client) && childSpan instanceof SentryNonRecordingSpan) {
if (parentSpan instanceof SentryNonRecordingSpan && parentSpan.dropReason) {
childSpan.dropReason = parentSpan.dropReason;
client.recordDroppedEvent(parentSpan.dropReason, "span");
} else if (!isTracingSuppressed) {
childSpan.dropReason = "sample_rate";
client.recordDroppedEvent("sample_rate", "span");
}
}
client.emit("spanStart", childSpan);
if (spanArguments.endTimestamp) {
client.emit("spanEnd", childSpan);
client.emit("afterSpanEnd", childSpan);
}
return childSpan;
}
function getParentSpan(scope, customParentSpan) {
if (customParentSpan) {
return customParentSpan;
}
if (customParentSpan === null) {
return void 0;
}
const span = _getSpanForScope(scope);
if (!span) {
return void 0;
}
const client = getClient();
const options = client ? client.getOptions() : {};
if (options.parentSpanIsAlwaysRootSpan) {
return getRootSpan(span);
}
return span;
}
function getActiveSpanWrapper(parentSpan) {
return parentSpan !== void 0 ? (callback) => {
return withActiveSpan(parentSpan, callback);
} : (callback) => callback();
}
function _shouldIgnoreStreamedSpan(client, spanArguments) {
const ignoreSpans = client?.getOptions().ignoreSpans;
if (!client || !hasSpanStreamingEnabled(client) || !ignoreSpans?.length) {
return false;
}
return shouldIgnoreSpan(
{
description: spanArguments.name || "",
op: spanArguments.attributes?.[SEMANTIC_ATTRIBUTE_SENTRY_OP] || spanArguments.op,
attributes: spanArguments.attributes
},
ignoreSpans
);
}
function _isIgnoredSpan(span) {
return span instanceof SentryNonRecordingSpan && span.dropReason === "ignored";
}
function _isTracingSuppressed(scope) {
return scope.getScopeData().sdkProcessingMetadata[SUPPRESS_TRACING_KEY] === true;
}
export { SUPPRESS_TRACING_KEY, continueTrace, startInactiveSpan, startNewTrace, startSpan, startSpanManual, suppressTracing, withActiveSpan };
//# sourceMappingURL=trace.js.map