feat: Add feature flag models and buffers#8078
Conversation
Introduces the internal foundation for feature flag evaluation tracking. The implementation adds a shared feature flag value/evaluation model plus dedicated buffers for scope and span storage. Scope storage keeps the latest unique evaluations up to the configured internal limit and evicts the oldest entry on overflow. Span storage has its own smaller per-span limit and rejects new flag names once full while still allowing updates to existing flags. This also wires the private storage into scope lifecycle behavior: - scope cloning copies feature flags without sharing future mutations - `clear()` clears feature flags - scope serialization includes the stored flag context - span internals can store and serialize feature flag evaluations as span data No public feature flag API is added in this PR; this is the private storage layer that later API and integration work will build on.
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 5614a54. Configure here.
📲 Install BuildsiOS
|
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 9b154ad | 1218.94 ms | 1253.60 ms | 34.66 ms |
| ce900e7 | 1212.40 ms | 1244.57 ms | 32.18 ms |
| 48fa69f | 1221.60 ms | 1251.52 ms | 29.92 ms |
| 67ea138 | 1212.54 ms | 1240.48 ms | 27.94 ms |
| d1ddc41 | 1236.42 ms | 1267.21 ms | 30.79 ms |
| 1770336 | 1225.09 ms | 1251.32 ms | 26.23 ms |
| eddca8a | 1226.17 ms | 1259.98 ms | 33.81 ms |
| d6af355 | 1214.83 ms | 1252.78 ms | 37.95 ms |
| 92bcc8f | 1233.43 ms | 1270.20 ms | 36.77 ms |
| bf10fe6 | 1226.00 ms | 1260.76 ms | 34.76 ms |
App size
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| 9b154ad | 24.14 KiB | 1.16 MiB | 1.13 MiB |
| ce900e7 | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| 48fa69f | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| 67ea138 | 24.14 KiB | 1.17 MiB | 1.15 MiB |
| d1ddc41 | 24.14 KiB | 1.17 MiB | 1.15 MiB |
| 1770336 | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| eddca8a | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| d6af355 | 24.14 KiB | 1.18 MiB | 1.15 MiB |
| 92bcc8f | 24.14 KiB | 1.15 MiB | 1.13 MiB |
| bf10fe6 | 24.14 KiB | 1.17 MiB | 1.15 MiB |
philprime
left a comment
There was a problem hiding this comment.
LGTM with comments to consider
| // swiftlint:disable missing_docs | ||
| import Foundation | ||
|
|
||
| final class SentryFeatureFlagBuffer { |
There was a problem hiding this comment.
I’d keep this as a class since it owns mutable state and handles its own locking. If this was a struct we’d still need a synchronized wrapper around it from both swift and objc, as i don't want to do syncing from every call site.
NinjaLikesCheez
left a comment
There was a problem hiding this comment.
LGTM - a few comments to consider
| guard maxSize > 0 else { | ||
| return | ||
| } | ||
| let evaluation = SentryFeatureFlagEvaluation(flag: name, result: value.asSentryFeatureFlagValue) |
There was a problem hiding this comment.
m: since flag is effectively used as a key, a Set or Dictionary may be a more effective storage. This would simplify the lookup and replacement logic and reduce the runtime complexity.
Ofc, this breaks .dropOldest ideally I'd recommend [OrderedSet])(https://github.com/apple/swift-collections/blob/main/Sources/OrderedCollections/OrderedSet/OrderedSet.swift) but since we can't pull in dependencies easily, we could wrap the evaluation in another object that holds the insertion date. Then we only hit the O(n) path if we need to remove the oldest evaluation.
There was a problem hiding this comment.
Since payload order matters, I kept the ordered array as the source of truth and added a [String: Int] lookup map. This avoids sorting during allEvaluations/serialization, which happens on the event/span serialization path, while still making keyed lookup/update cheap.
The only remaining O(n) paths are the ones that actually shift or reorder the array, which should be fine for max sizes 100/10. They are also not worse than before.
- faster insertion/lookup - index shifting paths keep same o(n) complexity

#skip-changelog
📜 Description
Adds shared feature flag models, buffer storage, and serialization support for feature flag evaluations.
This includes:
flagscontextflag.evaluation.*dataThe implementation intentionally minimizes Objective-C exposure. The feature flag models and buffer logic live in Swift, while Objective-C only keeps a private
SentryFeatureFlagStoragereference on scope/span internals so existing ObjC serialization paths can include the data.The API mirrors the metrics-style design by accepting typed feature flag values through an internal value abstraction, allowing us to support additional result types beyond booleans in the future without changing the storage and serialization model.
💡 Motivation and Context
Adds the shared model and buffering layer for feature flag evaluations.
Closes #7988
💚 How did you test it?
Added targeted tests.
📝 Checklist
You have to check all boxes before merging:
sendDefaultPIIis enabled.