UNPKG

@sentry/core

Version:
399 lines (396 loc) 14.5 kB
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