@sentry/core
Version:
Base implementation for all Sentry JavaScript SDKs
143 lines (118 loc) • 5.54 kB
JavaScript
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