Skip to content

CLOSED - add node support for generic featureFlagsIntegration and move utils to core #16537

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 42 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
70ccac1
Dup integration and utils to core, todos in index and integration
aliu39 Jun 10, 2025
6381f91
Import utils from core with _INTERNAL_ prefix
aliu39 Jun 10, 2025
a93b0e0
Delete old browser utils and integration and yarn fix
aliu39 Jun 10, 2025
4366e5a
Keep browser in docstr example
aliu39 Jun 10, 2025
868cd71
Export from same pkgs as zodErrorsIntegration, except remix, solidsta…
aliu39 Jun 10, 2025
2b9a867
Merge branch 'aliu/span-flags-v2' into aliu/move-ffs-to-core
aliu39 Jun 10, 2025
1611330
Add on error node tests. TODO export/import buffer sizes as _INTERNAL
aliu39 Jun 10, 2025
8124a40
Merge branch 'aliu/move-ffs-to-core' of https://github.com/getsentry/…
aliu39 Jun 10, 2025
f56cc3e
fix(sveltekit): Add import attribute for node exports (#16528)
eltigerchino Jun 11, 2025
70a57e6
chore: Add external contributor to CHANGELOG.md (#16543)
HazAT Jun 11, 2025
0b0942f
meta(changelog): Update changelog for 9.28.1
Lms24 Jun 11, 2025
1ce9e77
Merge pull request #16544 from getsentry/prepare-release/9.28.1
Lms24 Jun 11, 2025
111db4a
release: 9.28.1
getsentry-bot Jun 11, 2025
a72ca10
chore(otel-v2-tests): Fix formatting and linting errors (#16546)
Lms24 Jun 11, 2025
0ce6dc5
Merge branch 'release/9.28.1'
Jun 11, 2025
5fc0388
feat(ember): Stop warning for `onError` usage (#16547)
mydea Jun 11, 2025
91ee98d
Merge pull request #16548 from getsentry/master
github-actions[bot] Jun 11, 2025
fee86b0
fix(browser): Ensure `suppressTracing` does not leak when async (#16545)
mydea Jun 11, 2025
88e3f8f
feat(node): Allow to force activate `vercelAiIntegration` (#16551)
mydea Jun 11, 2025
eb59604
feat(deps): Bump @sentry/rollup-plugin from 3.4.0 to 3.5.0 (#16524)
dependabot[bot] Jun 11, 2025
ba3728d
feat(node): Introduce `ignoreLayersType` option to koa integration (#…
AbhiPrasad Jun 11, 2025
65310d5
chore: Document `process.env.DEBUG` in node integration tests README …
AbhiPrasad Jun 11, 2025
6fc18fe
feat(browser): Update `web-vitals` to 5.0.2 (#16492)
Lms24 Jun 12, 2025
6b656b4
fix(vue): Ensure root component render span always ends (#16488)
Lms24 Jun 12, 2025
cbfada0
test(node): Fix nestjs-11 E2E test by pinning version
mydea Jun 12, 2025
e26e334
test(trpc): Fix E2E test by pinning to 11.3.0
mydea Jun 12, 2025
038cb5d
test: Fix E2E tests by pinning dependencies (#16569)
mydea Jun 12, 2025
6882abf
meta(changelog): Update changelog for 9.29.0
Lms24 Jun 12, 2025
65d6bd4
chore(deps): Upgrade tpml to 1.0.5 (#16512)
AbhiPrasad Jun 12, 2025
3bb5ac8
Merge pull request #16563 from getsentry/prepare-release/9.29.0
Lms24 Jun 12, 2025
6d70326
release: 9.29.0
getsentry-bot Jun 12, 2025
9cce949
Add flags to attrs directly on eval
aliu39 Jun 12, 2025
52385ee
Rename util
aliu39 Jun 12, 2025
90d9289
Move FF type to core utils file
aliu39 Jun 12, 2025
bca65dd
Export buffer sizes from core as _INTERNAL_
aliu39 Jun 12, 2025
c92a9b0
feat(node): Ensure `modulesIntegration` works in more environments (#…
mydea Jun 13, 2025
afe49dd
Merge branch 'release/9.29.0'
Jun 13, 2025
2e4d243
Merge pull request #16576 from getsentry/master
github-actions[bot] Jun 13, 2025
d35030d
feat(node): Automatically enable `vercelAiIntegration` when `ai` modu…
mydea Jun 13, 2025
4e7c7ef
feat(browser): Add detail to measure spans and add regression tests (…
AbhiPrasad Jun 13, 2025
db2fa33
Remove allowEviction
aliu39 Jun 15, 2025
b3d746b
Merge branch 'develop' of https://github.com/getsentry/sentry-javascr…
aliu39 Jun 15, 2025
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
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
// Corresponds to constants in featureFlags.ts, in browser utils.
// Corresponds to constants in featureFlags.ts, in @sentry/core utils.
export const FLAG_BUFFER_SIZE = 100;
export const MAX_FLAGS_PER_SPAN = 10;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

const FLAG_BUFFER_SIZE = 100;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
sampleRate: 1.0,
transport: loggingTransport,
integrations: [Sentry.featureFlagsIntegration()],
});

const flagsIntegration = Sentry.getClient()?.getIntegrationByName<Sentry.FeatureFlagsIntegration>('FeatureFlags');
for (let i = 1; i <= FLAG_BUFFER_SIZE; i++) {
flagsIntegration?.addFeatureFlag(`feat${i}`, false);
}
flagsIntegration?.addFeatureFlag(`feat${FLAG_BUFFER_SIZE + 1}`, true); // eviction
flagsIntegration?.addFeatureFlag('feat3', true); // update

throw new Error('Test error');
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { afterAll, test } from 'vitest';
import { cleanupChildProcesses, createRunner } from '../../../../../utils/runner';

const FLAG_BUFFER_SIZE = 100;

afterAll(() => {
cleanupChildProcesses();
});

test('Flags captured on error with eviction, update, and no async tasks', async () => {
// Based on scenario.ts.
const expectedFlags = [{ flag: 'feat2', result: false }];
for (let i = 4; i <= FLAG_BUFFER_SIZE; i++) {
expectedFlags.push({ flag: `feat${i}`, result: false });
}
expectedFlags.push({ flag: `feat${FLAG_BUFFER_SIZE + 1}`, result: true });
expectedFlags.push({ flag: 'feat3', result: true });

await createRunner(__dirname, 'scenario.ts')
.expect({
event: {
exception: { values: [{ type: 'Error', value: 'Test error' }] },
contexts: {
flags: {
values: expectedFlags,
},
},
},
})
.start()
.completed();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import type { Scope } from '@sentry/node';
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
sampleRate: 1.0,
transport: loggingTransport,
integrations: [Sentry.featureFlagsIntegration()],
});

const flagsIntegration = Sentry.getClient()?.getIntegrationByName<Sentry.FeatureFlagsIntegration>('FeatureFlags');
flagsIntegration?.addFeatureFlag('shared', true);

Sentry.withScope((_scope: Scope) => {
flagsIntegration?.addFeatureFlag('forked', true);
flagsIntegration?.addFeatureFlag('shared', false);
Sentry.captureException(new Error('Error in forked scope'));
});

flagsIntegration?.addFeatureFlag('main', true);
throw new Error('Error in main scope');
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { afterAll, test } from 'vitest';
import { cleanupChildProcesses, createRunner } from '../../../../../utils/runner';

afterAll(() => {
cleanupChildProcesses();
});

test('Flags captured on error are isolated by current scope', async () => {
await createRunner(__dirname, 'scenario.ts')
.expect({
event: {
exception: { values: [{ type: 'Error', value: 'Error in forked scope' }] },
contexts: {
flags: {
values: [
{ flag: 'forked', result: true },
{ flag: 'shared', result: false },
],
},
},
},
})
.expect({
event: {
exception: { values: [{ type: 'Error', value: 'Error in main scope' }] },
contexts: {
flags: {
values: [
{ flag: 'shared', result: true },
{ flag: 'main', result: true },
],
},
},
},
})
.start()
.completed();
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as Sentry from '@sentry/node';
import { loggingTransport } from '@sentry-internal/node-integration-tests';

const MAX_FLAGS_PER_SPAN = 10;

Sentry.init({
dsn: 'https://public@dsn.ingest.sentry.io/1337',
sampleRate: 1.0,
// // disable attaching headers to /test/* endpoints
// tracePropagationTargets: [/^(?!.*test).*$/],
tracesSampleRate: 1.0,
transport: loggingTransport,
integrations: [Sentry.featureFlagsIntegration()],
});

const flagsIntegration = Sentry.getClient()?.getIntegrationByName<Sentry.FeatureFlagsIntegration>('FeatureFlags');

Sentry.startSpan({ name: 'test-root-span' }, () => {
Sentry.startSpan({ name: 'test-span' }, () => {
Sentry.startSpan({ name: 'test-nested-span' }, () => {
for (let i = 1; i <= MAX_FLAGS_PER_SPAN; i++) {
flagsIntegration?.addFeatureFlag(`feat${i}`, false);
}
flagsIntegration?.addFeatureFlag(`feat${MAX_FLAGS_PER_SPAN + 1}`, true); // dropped flag
flagsIntegration?.addFeatureFlag('feat3', true); // update
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { afterAll, expect, test } from 'vitest';
import { cleanupChildProcesses, createRunner } from '../../../../utils/runner';

const MAX_FLAGS_PER_SPAN = 10;

afterAll(() => {
cleanupChildProcesses();
});

test('Flags captured on span attributes with max limit', async () => {
// Based on scenario.ts.
const expectedFlags: Record<string, boolean> = {};
for (let i = 1; i <= MAX_FLAGS_PER_SPAN; i++) {
expectedFlags[`flag.evaluation.feat${i}`] = i === 3;
}

await createRunner(__dirname, 'scenario.ts')
.expect({
transaction: {
spans: [
expect.objectContaining({
description: 'test-span',
data: expect.objectContaining({}),
}),
expect.objectContaining({
description: 'test-nested-span',
data: expect.objectContaining(expectedFlags),
}),
],
},
})
.start()
.completed();
});
2 changes: 2 additions & 0 deletions packages/astro/src/index.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ export {
consoleLoggingIntegration,
wrapMcpServerWithSentry,
NODE_VERSION,
featureFlagsIntegration,
type FeatureFlagsIntegration,
} from '@sentry/node';

export { init } from './server/sdk';
Expand Down
2 changes: 2 additions & 0 deletions packages/aws-serverless/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ export {
consoleLoggingIntegration,
wrapMcpServerWithSentry,
NODE_VERSION,
featureFlagsIntegration,
type FeatureFlagsIntegration,
} from '@sentry/node';

export {
Expand Down
4 changes: 2 additions & 2 deletions packages/browser/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ export {
instrumentSupabaseClient,
zodErrorsIntegration,
thirdPartyErrorFilterIntegration,
featureFlagsIntegration,
} from '@sentry/core';
export type { Span } from '@sentry/core';
export type { Span, FeatureFlagsIntegration } from '@sentry/core';
export { makeBrowserOfflineTransport } from './transports/offline';
export { browserProfilingIntegration } from './profiling/integration';
export { spotlightBrowserIntegration } from './integrations/spotlight';
export { browserSessionIntegration } from './integrations/browsersession';
export { featureFlagsIntegration, type FeatureFlagsIntegration } from './integrations/featureFlags';
export { launchDarklyIntegration, buildLaunchDarklyFlagUsedHandler } from './integrations/featureFlags/launchdarkly';
export { openFeatureIntegration, OpenFeatureIntegrationHook } from './integrations/featureFlags/openfeature';
export { unleashIntegration } from './integrations/featureFlags/unleash';
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { Client, Event, EventHint, IntegrationFn, Span } from '@sentry/core';
import { defineIntegration } from '@sentry/core';
import {
bufferSpanFeatureFlag,
copyFlagsFromScopeToEvent,
freezeSpanFeatureFlags,
insertFlagToScope,
} from '../../../utils/featureFlags';
_INTERNAL_bufferSpanFeatureFlag,
_INTERNAL_copyFlagsFromScopeToEvent,
_INTERNAL_freezeSpanFeatureFlags,
_INTERNAL_insertFlagToScope,
defineIntegration,
} from '@sentry/core';
import type { LDContext, LDEvaluationDetail, LDInspectionFlagUsedHandler } from './types';

/**
Expand All @@ -29,12 +29,12 @@ export const launchDarklyIntegration = defineIntegration(() => {

setup(client: Client) {
client.on('spanEnd', (span: Span) => {
freezeSpanFeatureFlags(span);
_INTERNAL_freezeSpanFeatureFlags(span);
});
},

processEvent(event: Event, _hint: EventHint, _client: Client): Event {
return copyFlagsFromScopeToEvent(event);
return _INTERNAL_copyFlagsFromScopeToEvent(event);
},
};
}) satisfies IntegrationFn;
Expand All @@ -56,8 +56,8 @@ 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) => {
insertFlagToScope(flagKey, flagDetail.value);
bufferSpanFeatureFlag(flagKey, flagDetail.value);
_INTERNAL_insertFlagToScope(flagKey, flagDetail.value);
_INTERNAL_bufferSpanFeatureFlag(flagKey, flagDetail.value);
},
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@
* ```
*/
import type { Client, Event, EventHint, IntegrationFn, Span } from '@sentry/core';
import { defineIntegration } from '@sentry/core';
import {
bufferSpanFeatureFlag,
copyFlagsFromScopeToEvent,
freezeSpanFeatureFlags,
insertFlagToScope,
} from '../../../utils/featureFlags';
_INTERNAL_bufferSpanFeatureFlag,
_INTERNAL_copyFlagsFromScopeToEvent,
_INTERNAL_freezeSpanFeatureFlags,
_INTERNAL_insertFlagToScope,
defineIntegration,
} from '@sentry/core';
import type { EvaluationDetails, HookContext, HookHints, JsonValue, OpenFeatureHook } from './types';

export const openFeatureIntegration = defineIntegration(() => {
Expand All @@ -29,12 +29,12 @@ export const openFeatureIntegration = defineIntegration(() => {

setup(client: Client) {
client.on('spanEnd', (span: Span) => {
freezeSpanFeatureFlags(span);
_INTERNAL_freezeSpanFeatureFlags(span);
});
},

processEvent(event: Event, _hint: EventHint, _client: Client): Event {
return copyFlagsFromScopeToEvent(event);
return _INTERNAL_copyFlagsFromScopeToEvent(event);
},
};
}) satisfies IntegrationFn;
Expand All @@ -47,15 +47,15 @@ export class OpenFeatureIntegrationHook implements OpenFeatureHook {
* Successful evaluation result.
*/
public after(_hookContext: Readonly<HookContext<JsonValue>>, evaluationDetails: EvaluationDetails<JsonValue>): void {
insertFlagToScope(evaluationDetails.flagKey, evaluationDetails.value);
bufferSpanFeatureFlag(evaluationDetails.flagKey, evaluationDetails.value);
_INTERNAL_insertFlagToScope(evaluationDetails.flagKey, evaluationDetails.value);
_INTERNAL_bufferSpanFeatureFlag(evaluationDetails.flagKey, evaluationDetails.value);
}

/**
* On error evaluation result.
*/
public error(hookContext: Readonly<HookContext<JsonValue>>, _error: unknown, _hookHints?: HookHints): void {
insertFlagToScope(hookContext.flagKey, hookContext.defaultValue);
bufferSpanFeatureFlag(hookContext.flagKey, hookContext.defaultValue);
_INTERNAL_insertFlagToScope(hookContext.flagKey, hookContext.defaultValue);
_INTERNAL_bufferSpanFeatureFlag(hookContext.flagKey, hookContext.defaultValue);
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { Client, Event, EventHint, IntegrationFn, Span } from '@sentry/core';
import { defineIntegration } from '@sentry/core';
import {
bufferSpanFeatureFlag,
copyFlagsFromScopeToEvent,
freezeSpanFeatureFlags,
insertFlagToScope,
} from '../../../utils/featureFlags';
_INTERNAL_bufferSpanFeatureFlag,
_INTERNAL_copyFlagsFromScopeToEvent,
_INTERNAL_freezeSpanFeatureFlags,
_INTERNAL_insertFlagToScope,
defineIntegration,
} from '@sentry/core';
import type { FeatureGate, StatsigClient } from './types';

/**
Expand Down Expand Up @@ -38,17 +38,17 @@ export const statsigIntegration = defineIntegration(

setup(client: Client) {
client.on('spanEnd', (span: Span) => {
freezeSpanFeatureFlags(span);
_INTERNAL_freezeSpanFeatureFlags(span);
});

statsigClient.on('gate_evaluation', (event: { gate: FeatureGate }) => {
insertFlagToScope(event.gate.name, event.gate.value);
bufferSpanFeatureFlag(event.gate.name, event.gate.value);
_INTERNAL_insertFlagToScope(event.gate.name, event.gate.value);
_INTERNAL_bufferSpanFeatureFlag(event.gate.name, event.gate.value);
});
},

processEvent(event: Event, _hint: EventHint, _client: Client): Event {
return copyFlagsFromScopeToEvent(event);
return _INTERNAL_copyFlagsFromScopeToEvent(event);
},
};
},
Expand Down
Loading
Loading