UNPKG

@sentry/core

Version:
143 lines (118 loc) 5.54 kB
import { DEFAULT_ENVIRONMENT } from '../constants.js'; import { getClient } from '../currentScopes.js'; import { SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE } from '../semanticAttributes.js'; import { baggageHeaderToDynamicSamplingContext, dynamicSamplingContextToSentryBaggageHeader } from '../utils-hoist/baggage.js'; import { addNonEnumerableProperty } from '../utils-hoist/object.js'; import { hasSpansEnabled } from '../utils/hasSpansEnabled.js'; import { getRootSpan, spanToJSON, spanIsSampled } from '../utils/spanUtils.js'; import { getCapturedScopesOnSpan } from './utils.js'; /** * If you change this value, also update the terser plugin config to * avoid minification of the object property! */ const FROZEN_DSC_FIELD = '_frozenDsc'; /** * Freeze the given DSC on the given span. */ function freezeDscOnSpan(span, dsc) { const spanWithMaybeDsc = span ; addNonEnumerableProperty(spanWithMaybeDsc, FROZEN_DSC_FIELD, dsc); } /** * Creates a dynamic sampling context from a client. * * Dispatches the `createDsc` lifecycle hook as a side effect. */ function getDynamicSamplingContextFromClient(trace_id, client) { const options = client.getOptions(); const { publicKey: public_key } = client.getDsn() || {}; // Instead of conditionally adding non-undefined values, we add them and then remove them if needed // otherwise, the order of baggage entries changes, which "breaks" a bunch of tests etc. const dsc = { environment: options.environment || DEFAULT_ENVIRONMENT, release: options.release, public_key, trace_id, }; client.emit('createDsc', dsc); return dsc; } /** * Get the dynamic sampling context for the currently active scopes. */ function getDynamicSamplingContextFromScope(client, scope) { const propagationContext = scope.getPropagationContext(); return propagationContext.dsc || getDynamicSamplingContextFromClient(propagationContext.traceId, client); } /** * Creates a dynamic sampling context from a span (and client and scope) * * @param span the span from which a few values like the root span name and sample rate are extracted. * * @returns a dynamic sampling context */ function getDynamicSamplingContextFromSpan(span) { const client = getClient(); if (!client) { return {}; } const rootSpan = getRootSpan(span); const rootSpanJson = spanToJSON(rootSpan); const rootSpanAttributes = rootSpanJson.data; const traceState = rootSpan.spanContext().traceState; // The span sample rate that was locally applied to the root span should also always be applied to the DSC, even if the DSC is frozen. // This is so that the downstream traces/services can use parentSampleRate in their `tracesSampler` to make consistent sampling decisions across the entire trace. const rootSpanSampleRate = traceState?.get('sentry.sample_rate') ?? rootSpanAttributes[SEMANTIC_ATTRIBUTE_SENTRY_SAMPLE_RATE]; function applyLocalSampleRateToDsc(dsc) { if (typeof rootSpanSampleRate === 'number' || typeof rootSpanSampleRate === 'string') { dsc.sample_rate = `${rootSpanSampleRate}`; } return dsc; } // For core implementation, we freeze the DSC onto the span as a non-enumerable property const frozenDsc = (rootSpan )[FROZEN_DSC_FIELD]; if (frozenDsc) { return applyLocalSampleRateToDsc(frozenDsc); } // For OpenTelemetry, we freeze the DSC on the trace state const traceStateDsc = traceState?.get('sentry.dsc'); // If the span has a DSC, we want it to take precedence const dscOnTraceState = traceStateDsc && baggageHeaderToDynamicSamplingContext(traceStateDsc); if (dscOnTraceState) { return applyLocalSampleRateToDsc(dscOnTraceState); } // Else, we generate it from the span const dsc = getDynamicSamplingContextFromClient(span.spanContext().traceId, client); // We don't want to have a transaction name in the DSC if the source is "url" because URLs might contain PII const source = rootSpanAttributes[SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]; // after JSON conversion, txn.name becomes jsonSpan.description const name = rootSpanJson.description; if (source !== 'url' && name) { dsc.transaction = name; } // How can we even land here with hasSpansEnabled() returning false? // Otel creates a Non-recording span in Tracing Without Performance mode when handling incoming requests // So we end up with an active span that is not sampled (neither positively nor negatively) if (hasSpansEnabled()) { dsc.sampled = String(spanIsSampled(rootSpan)); dsc.sample_rand = // In OTEL we store the sample rand on the trace state because we cannot access scopes for NonRecordingSpans // The Sentry OTEL SpanSampler takes care of writing the sample rand on the root span traceState?.get('sentry.sample_rand') ?? // On all other platforms we can actually get the scopes from a root span (we use this as a fallback) getCapturedScopesOnSpan(rootSpan).scope?.getPropagationContext().sampleRand.toString(); } applyLocalSampleRateToDsc(dsc); client.emit('createDsc', dsc, rootSpan); return dsc; } /** * Convert a Span to a baggage header. */ function spanToBaggageHeader(span) { const dsc = getDynamicSamplingContextFromSpan(span); return dynamicSamplingContextToSentryBaggageHeader(dsc); } export { freezeDscOnSpan, getDynamicSamplingContextFromClient, getDynamicSamplingContextFromScope, getDynamicSamplingContextFromSpan, spanToBaggageHeader }; //# sourceMappingURL=dynamicSamplingContext.js.map