Skip to content

Conversation

adinauer
Copy link
Member

@adinauer adinauer commented Oct 16, 2025

📜 Description

addFeatureFlag can be used to track feature flag evaluations and have them show up on errors in Sentry

💡 Motivation and Context

Scope based part of #4763

💚 How did you test it?

Manually + E2E tests + unit tests

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

🔮 Next steps

  • Test (de)serialization
  • Add maxFeatureFlags to external options (sentry.properties)
  • Add span based API
  • Make params on addFeatureFlag nullable

Copy link
Contributor

github-actions bot commented Oct 16, 2025

Fails
🚫 Please consider adding a changelog entry for the next release.
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Instructions and example for changelog

Please add an entry to CHANGELOG.md to the "Unreleased" section. Make sure the entry includes this PR's number.

Example:

## Unreleased

### Features

- Add scope based feature flags ([#4812](https://github.com/getsentry/sentry-java/pull/4812))

If none of the above apply, you can opt out of this check by adding #skip-changelog to the PR description or adding a skip-changelog label.

Generated by 🚫 dangerJS against 74cc427

cursor[bot]

This comment was marked as outdated.

@ApiStatus.Internal
void replaceOptions(final @NotNull SentryOptions options);

void addFeatureFlag(final @NotNull String flag, final boolean result);
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 wanna make params @Nullable in a follow up to improve DX by not requiring customers to check before calling and potentially causing NPEs. I'll just make it noop in that case.

@ApiStatus.Internal
@Override
/** Not intended to be set on a scopes Context directly */
public void setFeatureFlags(@NotNull FeatureFlags spring) {
Copy link

Choose a reason for hiding this comment

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

Bug: Incorrect Parameter Name Causes Confusion

The setFeatureFlags method's parameter is named spring instead of featureFlags. This looks like a copy-paste error from the setSpring method, which makes the code confusing.

Fix in Cursor Fix in Web

Copy link
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 369.83 ms 412.26 ms 42.43 ms
Size 1.58 MiB 2.11 MiB 540.70 KiB

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
23d6b12 354.10 ms 408.38 ms 54.28 ms
9fbb112 361.43 ms 427.57 ms 66.14 ms
1df7eb6 397.04 ms 429.64 ms 32.60 ms
c8125f3 397.65 ms 485.14 ms 87.49 ms
ee747ae 358.21 ms 389.41 ms 31.20 ms
b750b96 408.98 ms 480.32 ms 71.34 ms
f634d01 359.58 ms 433.88 ms 74.30 ms
806307f 357.85 ms 424.64 ms 66.79 ms
ee747ae 374.71 ms 455.18 ms 80.47 ms
ce0a49e 532.00 ms 609.96 ms 77.96 ms

App size

Revision Plain With Sentry Diff
23d6b12 1.58 MiB 2.10 MiB 532.31 KiB
9fbb112 1.58 MiB 2.11 MiB 539.18 KiB
1df7eb6 1.58 MiB 2.10 MiB 532.97 KiB
c8125f3 1.58 MiB 2.10 MiB 532.32 KiB
ee747ae 1.58 MiB 2.10 MiB 530.95 KiB
b750b96 1.58 MiB 2.10 MiB 533.19 KiB
f634d01 1.58 MiB 2.10 MiB 533.40 KiB
806307f 1.58 MiB 2.10 MiB 533.42 KiB
ee747ae 1.58 MiB 2.10 MiB 530.95 KiB
ce0a49e 1.58 MiB 2.10 MiB 532.94 KiB

tmpList.remove(0);
}

flags = new CopyOnWriteArrayList<>(tmpList);
Copy link
Member

Choose a reason for hiding this comment

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

Does it need to be CopyOnWrite if we never actually call flags.add()?

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 opted for CopyOnWriteArrayList due to it being a good choice for scope cloning without affecting parent scopes.

Copy link
Member Author

Choose a reason for hiding this comment

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

Gotta think about whether using something else would work too, probably yes but what's the benefit?

Copy link
Member Author

Choose a reason for hiding this comment

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

Should be possible to replace with ArrayList but then we must never modify the collection. I was still hoping for some enlightenment on how to speed up the add method. I've written this down and depending on how the add method turns out, we can replace it.

We could in theory wrap it with Collections.unmodifyableList but I'd rather send corrupted data as opposed to crashing the customers application.

Copy link
Member

Choose a reason for hiding this comment

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

but then we must never modify the collection.

why is that? sorry for the questions I guess i'm missing some context 😅

private int maxBreadcrumbs = 100;

/**
* This variable controls the total amount of feature flags that should be captured Default is 100
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
* This variable controls the total amount of feature flags that should be captured Default is 100
* This variable controls the total amount of feature flag evaluations that should be stored on the scope.
* The most recent `maxFeatureFlags` evaluations are stored on each scope.
* Default is 100

private @NotNull String flag;

/** Evaluation result of the feature flag. */
private boolean result;
Copy link
Member

Choose a reason for hiding this comment

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

Flag evaluation is allowed to be an arbitrary JSON according to Relay https://github.com/getsentry/relay/blob/21c2e18ebe6f834a4ce4e149c6a43e4bec1e90f8/relay-event-schema/src/protocol/contexts/flags.rs#L30
However in docs and frontend this is typed as bool.
I think it will be easy to extend this with an overload that takes Object or we just change this to take it into account from the get go.
Let's also double check with @cmanallen on how to proceed here.

Copy link
Member

Choose a reason for hiding this comment

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

Relay is set up to accommodate remote-configs/feature-flags of any JSON type. The UI currently expects boolean though. I'm not sure how resilient it is to non-boolean types. The goal is to expand our UI to accommodate more types in the future but AFAIK that's not been realized yet. Something I can find out later today.

Comment on lines +43 to +56
final int size = flags.size();
final @NotNull ArrayList<FeatureFlagEntry> tmpList = new ArrayList<>(size + 1);
for (FeatureFlagEntry entry : flags) {
if (!entry.flag.equals(flag)) {
tmpList.add(entry);
}
}
tmpList.add(new FeatureFlagEntry(flag, result, System.nanoTime()));

if (tmpList.size() > maxSize) {
tmpList.remove(0);
}

flags = new CopyOnWriteArrayList<>(tmpList);
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
final int size = flags.size();
final @NotNull ArrayList<FeatureFlagEntry> tmpList = new ArrayList<>(size + 1);
for (FeatureFlagEntry entry : flags) {
if (!entry.flag.equals(flag)) {
tmpList.add(entry);
}
}
tmpList.add(new FeatureFlagEntry(flag, result, System.nanoTime()));
if (tmpList.size() > maxSize) {
tmpList.remove(0);
}
flags = new CopyOnWriteArrayList<>(tmpList);
final int size = flags.size();
for (int i = 0; i < size; i++) {
if (flags.get(i).equals(flag)) {
flags.remove(i);
break;
}
}
flags.add(new FeatureFlagEntry(flag, result, System.nanoTime()));
if (flags.size() > maxSize) {
flags.remove(0);
}

Maybe this is better?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants