-
-
Notifications
You must be signed in to change notification settings - Fork 459
Add scope based feature flags #4812
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
base: main
Are you sure you want to change the base?
Conversation
Instructions and example for changelogPlease add an entry to 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 |
@ApiStatus.Internal | ||
void replaceOptions(final @NotNull SentryOptions options); | ||
|
||
void addFeatureFlag(final @NotNull String flag, final boolean result); |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Performance metrics 🚀
|
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); |
There was a problem hiding this comment.
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()
?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
* 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; |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
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); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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?
📜 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
sendDefaultPII
is enabled.🔮 Next steps
sentry.properties
)