Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Next Next commit
ref(flags): Refactor LaunchDarkly integration to reusable functions
Moves core logic into re-usable functions so that we can re-use for other integrations.
  • Loading branch information
billyvg committed Nov 20, 2024
commit c8e8c21dc68ab7019c036f5a5b9b64e0e1931f19
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import type { Client, Event, EventHint, IntegrationFn } from '@sentry/types';
import type { LDContext, LDEvaluationDetail, LDInspectionFlagUsedHandler } from './types';

import { defineIntegration, getCurrentScope } from '@sentry/core';
import { insertToFlagBuffer } from '../../../utils/featureFlags';
import { defineIntegration } from '@sentry/core';
import { copyFlagsFromScopeToEvent, insertToFlagBuffer } from '../../../utils/featureFlags';

/**
* Sentry integration for capturing feature flags from LaunchDarkly.
Expand All @@ -24,15 +24,7 @@ export const launchDarklyIntegration = defineIntegration(() => {
name: 'LaunchDarkly',

processEvent(event: Event, _hint: EventHint, _client: Client): Event {
const scope = getCurrentScope();
const flagContext = scope.getScopeData().contexts.flags;
const flagBuffer = flagContext ? flagContext.values : [];

if (event.contexts === undefined) {
event.contexts = {};
}
event.contexts.flags = { values: [...flagBuffer] };
return event;
return copyFlagsFromScopeToEvent(event);
},
};
}) satisfies IntegrationFn;
Expand All @@ -54,15 +46,7 @@ export function buildLaunchDarklyFlagUsedHandler(): LDInspectionFlagUsedHandler
* Handle a flag evaluation by storing its name and value on the current scope.
*/
method: (flagKey: string, flagDetail: LDEvaluationDetail, _context: LDContext) => {
if (typeof flagDetail.value === 'boolean') {
const scopeContexts = getCurrentScope().getScopeData().contexts;
if (!scopeContexts.flags) {
scopeContexts.flags = { values: [] };
}
const flagBuffer = scopeContexts.flags.values;
insertToFlagBuffer(flagBuffer, flagKey, flagDetail.value);
}
return;
insertToFlagBuffer(flagKey, flagDetail.value);
},
};
}
38 changes: 29 additions & 9 deletions packages/browser/src/utils/featureFlags.ts
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine we'll want to use this in node/server environments as well, would it make sense to move these to core?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the handler being used in the LD integration is only available for client-side js, that's why this was moved to the browser. Not sure if this will change for other integrations in the future

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing in these util functions are specific to LD integration, it stores flag values to scope. It's possible for a server side implementation to re-use these functions.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { logger } from '@sentry/core';
import type { FeatureFlag } from '@sentry/types';
import { getCurrentScope, logger } from '@sentry/core';
import type { Event, FeatureFlag } from '@sentry/types';
import { DEBUG_BUILD } from '../debug-build';

/**
Expand All @@ -13,6 +13,21 @@ import { DEBUG_BUILD } from '../debug-build';
*/
export const FLAG_BUFFER_SIZE = 100;

/**
* Copies feature flags that are in current scope context to the event context
*/
export function copyFlagsFromScopeToEvent(event: Event): Event {
const scope = getCurrentScope();
const flagContext = scope.getScopeData().contexts.flags;
const flagBuffer = flagContext ? flagContext.values : [];

if (event.contexts === undefined) {
event.contexts = {};
}
event.contexts.flags = { values: [...flagBuffer] };
return event;
}

/**
* Insert into a FeatureFlag array while maintaining ordered LRU properties. Not
* thread-safe. After inserting:
Expand All @@ -21,18 +36,23 @@ export const FLAG_BUFFER_SIZE = 100;
* - The length of `flags` does not exceed `maxSize`. The oldest flag is evicted
* as needed.
*
* @param flags The array to insert 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. It's recommended
* to keep this consistent across insertions. Default is DEFAULT_MAX_SIZE
*/
export function insertToFlagBuffer(
flags: FeatureFlag[],
name: string,
value: boolean,
maxSize: number = FLAG_BUFFER_SIZE,
): void {
export function insertToFlagBuffer(name: string, value: unknown, maxSize: number = FLAG_BUFFER_SIZE): void {
// Currently only accepts boolean values
if (typeof value !== 'boolean') {
return;
}

const scopeContexts = getCurrentScope().getScopeData().contexts;
if (!scopeContexts.flags) {
scopeContexts.flags = { values: [] };
}
const flags = scopeContexts.flags.values as FeatureFlag[];

if (flags.length > maxSize) {
DEBUG_BUILD && logger.error(`[Feature Flags] insertToFlagBuffer called on a buffer larger than maxSize=${maxSize}`);
return;
Expand Down