@sentry/core
Version:
Base implementation for all Sentry JavaScript SDKs
152 lines (133 loc) • 5.05 kB
JavaScript
import { getCurrentScope } from '../currentScopes.js';
import { DEBUG_BUILD } from '../debug-build.js';
import { logger } from './logger.js';
import { GLOBAL_OBJ } from './worldwide.js';
import { getActiveSpan } from './spanUtils.js';
/**
* Ordered LRU cache for storing feature flags in the scope context. The name
* of each flag in the buffer is unique, and the output of getAll() is ordered
* from oldest to newest.
*/
/**
* Max size of the LRU flag buffer stored in Sentry scope and event contexts.
*/
const _INTERNAL_FLAG_BUFFER_SIZE = 100;
/**
* Max number of flag evaluations to record per span.
*/
const _INTERNAL_MAX_FLAGS_PER_SPAN = 10;
// Global map of spans to feature flag buffers. Populated by feature flag integrations.
GLOBAL_OBJ._spanToFlagBufferMap = new WeakMap();
const SPAN_FLAG_ATTRIBUTE_PREFIX = 'flag.evaluation.';
/**
* Copies feature flags that are in current scope context to the event context
*/
function _INTERNAL_copyFlagsFromScopeToEvent(event) {
const scope = getCurrentScope();
const flagContext = scope.getScopeData().contexts.flags;
const flagBuffer = flagContext ? flagContext.values : [];
if (!flagBuffer.length) {
return event;
}
if (event.contexts === undefined) {
event.contexts = {};
}
event.contexts.flags = { values: [...flagBuffer] };
return event;
}
/**
* Inserts a flag into the current scope's context while maintaining ordered LRU properties.
* Not thread-safe. After inserting:
* - The flag buffer is sorted in order of recency, with the newest evaluation at the end.
* - The names in the buffer are always unique.
* - The length of the buffer never exceeds `maxSize`.
*
* @param name Name of the feature flag to insert.
* @param value Value of the feature flag.
* @param maxSize Max number of flags the buffer should store. Default value should always be used in production.
*/
function _INTERNAL_insertFlagToScope(
name,
value,
maxSize = _INTERNAL_FLAG_BUFFER_SIZE,
) {
const scopeContexts = getCurrentScope().getScopeData().contexts;
if (!scopeContexts.flags) {
scopeContexts.flags = { values: [] };
}
const flags = scopeContexts.flags.values ;
_INTERNAL_insertToFlagBuffer(flags, name, value, maxSize);
}
/**
* Exported for tests only. Currently only accepts boolean values (otherwise no-op).
* Inserts a flag into a FeatureFlag array while maintaining the following properties:
* - Flags are sorted in order of recency, with the newest evaluation at the end.
* - The flag names are always unique.
* - The length of the array never exceeds `maxSize`.
*
* @param flags The buffer to insert the flag into.
* @param name Name of the feature flag to insert.
* @param value Value of the feature flag.
* @param maxSize Max number of flags the buffer should store. Default value should always be used in production.
*/
function _INTERNAL_insertToFlagBuffer(
flags,
name,
value,
maxSize,
) {
if (typeof value !== 'boolean') {
return;
}
if (flags.length > maxSize) {
DEBUG_BUILD && logger.error(`[Feature Flags] insertToFlagBuffer called on a buffer larger than maxSize=${maxSize}`);
return;
}
// Check if the flag is already in the buffer - O(n)
const index = flags.findIndex(f => f.flag === name);
if (index !== -1) {
// The flag was found, remove it from its current position - O(n)
flags.splice(index, 1);
}
if (flags.length === maxSize) {
// If at capacity, pop the earliest flag - O(n)
flags.shift();
}
// Push the flag to the end - O(1)
flags.push({
flag: name,
result: value,
});
}
/**
* Records a feature flag evaluation for the active span. This is a no-op for non-boolean values.
* The flag and its value is stored in span attributes with the `flag.evaluation` prefix. Once the
* unique flags for a span reaches maxFlagsPerSpan, subsequent flags are dropped.
*
* @param name Name of the feature flag.
* @param value Value of the feature flag. Non-boolean values are ignored.
* @param maxFlagsPerSpan Max number of flags a buffer should store. Default value should always be used in production.
*/
function _INTERNAL_addFeatureFlagToActiveSpan(
name,
value,
maxFlagsPerSpan = _INTERNAL_MAX_FLAGS_PER_SPAN,
) {
const spanFlagMap = GLOBAL_OBJ._spanToFlagBufferMap;
if (!spanFlagMap || typeof value !== 'boolean') {
return;
}
const span = getActiveSpan();
if (span) {
const flags = spanFlagMap.get(span) || new Set();
if (flags.has(name)) {
span.setAttribute(`${SPAN_FLAG_ATTRIBUTE_PREFIX}${name}`, value);
} else if (flags.size < maxFlagsPerSpan) {
flags.add(name);
span.setAttribute(`${SPAN_FLAG_ATTRIBUTE_PREFIX}${name}`, value);
}
spanFlagMap.set(span, flags);
}
}
export { _INTERNAL_FLAG_BUFFER_SIZE, _INTERNAL_MAX_FLAGS_PER_SPAN, _INTERNAL_addFeatureFlagToActiveSpan, _INTERNAL_copyFlagsFromScopeToEvent, _INTERNAL_insertFlagToScope, _INTERNAL_insertToFlagBuffer };
//# sourceMappingURL=featureFlags.js.map