-
Notifications
You must be signed in to change notification settings - Fork 555
feat(flags): Add integration for custom tracking of flag evaluations #3860
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
Merged
Merged
Changes from 15 commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
3652ce3
Add new integration and unit tests
aliu39 625969e
Test flag values for LD and OF threaded/asyncio, not just flag names
aliu39 beb9512
update ffIntegration test to be e2e, and fix LRU copy bug
aliu39 296545d
Merge branch 'master' into aliu/ff-integration-and-added-coverage
aliu39 921b133
make a helper fixture and test error processor in original thread
aliu39 4651b6a
Move api to top-level, rename to add_flag
aliu39 36bc869
Add docstrs
aliu39 81c1761
Rename to add_feature_flag
aliu39 42f76a3
Rm extra import in test_lru_cache
aliu39 ef01990
Revert lru comment
aliu39 381ccc1
Type annotate
aliu39 c76192e
Review comments
aliu39 b63982b
Update launchdarkly and openfeature tests to be e2e
aliu39 af1128c
Merge branch 'master' into aliu/ff-integration-and-added-coverage
aliu39 4c6f08a
Merge branch 'master' into aliu/ff-integration-and-added-coverage
antonpirker b4eb421
Update docstrs
aliu39 043e298
Skip threading test for <3.7
aliu39 41fdde5
Merge branch 'master' into aliu/ff-integration-and-added-coverage
aliu39 2dc679d
Skip ffs asyncio test if 3.6
aliu39 af9e92d
undo 'skip threading test'
aliu39 2cea37b
Try commenting out asyncio
aliu39 fea761c
Use importorskip
aliu39 752ce7d
Import order
aliu39 d77ebc3
Also use skipif
aliu39 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
from sentry_sdk.flag_utils import flag_error_processor | ||
|
||
import sentry_sdk | ||
from sentry_sdk.integrations import Integration | ||
|
||
|
||
class FeatureFlagsIntegration(Integration): | ||
""" | ||
Sentry integration for buffering feature flags manually with an API and capturing them on | ||
error events. We recommend you do this on each flag evaluation. Flags are buffered per Sentry | ||
scope. | ||
|
||
See the [feature flag documentation](https://develop.sentry.dev/sdk/expected-features/#feature-flags) | ||
for more information. | ||
|
||
@example | ||
``` | ||
import sentry_sdk | ||
from sentry_sdk.integrations.featureflags import FeatureFlagsIntegration, add_feature_flag | ||
|
||
sentry_sdk.init(dsn="my_dsn", integrations=[FeatureFlagsIntegration()]); | ||
|
||
add_feature_flag('my-flag', true); | ||
sentry_sdk.capture_exception(Exception('broke')); // 'my-flag' should be captured on this Sentry event. | ||
``` | ||
""" | ||
|
||
identifier = "featureflags" | ||
|
||
@staticmethod | ||
def setup_once(): | ||
# type: () -> None | ||
scope = sentry_sdk.get_current_scope() | ||
scope.add_error_processor(flag_error_processor) | ||
|
||
|
||
def add_feature_flag(flag, result): | ||
# type: (str, bool) -> None | ||
""" | ||
Records a flag and its value to be sent on subsequent error events. We recommend you do this | ||
on flag evaluations. Flags are buffered per Sentry scope and limited to 100 per event. | ||
aliu39 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
""" | ||
flags = sentry_sdk.get_current_scope().flags | ||
flags.set(flag, result) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
import asyncio | ||
import concurrent.futures as cf | ||
|
||
import sentry_sdk | ||
from sentry_sdk.integrations.featureflags import ( | ||
FeatureFlagsIntegration, | ||
add_feature_flag, | ||
) | ||
|
||
|
||
def test_featureflags_integration(sentry_init, capture_events, uninstall_integration): | ||
uninstall_integration(FeatureFlagsIntegration.identifier) | ||
sentry_init(integrations=[FeatureFlagsIntegration()]) | ||
|
||
add_feature_flag("hello", False) | ||
add_feature_flag("world", True) | ||
add_feature_flag("other", False) | ||
|
||
events = capture_events() | ||
sentry_sdk.capture_exception(Exception("something wrong!")) | ||
|
||
assert len(events) == 1 | ||
assert events[0]["contexts"]["flags"] == { | ||
"values": [ | ||
{"flag": "hello", "result": False}, | ||
{"flag": "world", "result": True}, | ||
{"flag": "other", "result": False}, | ||
] | ||
} | ||
|
||
|
||
def test_featureflags_integration_threaded( | ||
sentry_init, capture_events, uninstall_integration | ||
): | ||
uninstall_integration(FeatureFlagsIntegration.identifier) | ||
sentry_init(integrations=[FeatureFlagsIntegration()]) | ||
events = capture_events() | ||
|
||
# Capture an eval before we split isolation scopes. | ||
add_feature_flag("hello", False) | ||
|
||
def task(flag_key): | ||
# Creates a new isolation scope for the thread. | ||
# This means the evaluations in each task are captured separately. | ||
with sentry_sdk.isolation_scope(): | ||
add_feature_flag(flag_key, False) | ||
# use a tag to identify to identify events later on | ||
sentry_sdk.set_tag("task_id", flag_key) | ||
sentry_sdk.capture_exception(Exception("something wrong!")) | ||
|
||
# Run tasks in separate threads | ||
with cf.ThreadPoolExecutor(max_workers=2) as pool: | ||
pool.map(task, ["world", "other"]) | ||
|
||
# Capture error in original scope | ||
sentry_sdk.set_tag("task_id", "0") | ||
sentry_sdk.capture_exception(Exception("something wrong!")) | ||
|
||
assert len(events) == 3 | ||
events.sort(key=lambda e: e["tags"]["task_id"]) | ||
|
||
assert events[0]["contexts"]["flags"] == { | ||
"values": [ | ||
{"flag": "hello", "result": False}, | ||
] | ||
} | ||
assert events[1]["contexts"]["flags"] == { | ||
"values": [ | ||
{"flag": "hello", "result": False}, | ||
{"flag": "other", "result": False}, | ||
] | ||
} | ||
assert events[2]["contexts"]["flags"] == { | ||
"values": [ | ||
{"flag": "hello", "result": False}, | ||
{"flag": "world", "result": False}, | ||
] | ||
} | ||
|
||
|
||
def test_featureflags_integration_asyncio( | ||
sentry_init, capture_events, uninstall_integration | ||
): | ||
uninstall_integration(FeatureFlagsIntegration.identifier) | ||
sentry_init(integrations=[FeatureFlagsIntegration()]) | ||
events = capture_events() | ||
|
||
# Capture an eval before we split isolation scopes. | ||
add_feature_flag("hello", False) | ||
|
||
async def task(flag_key): | ||
# Creates a new isolation scope for the thread. | ||
# This means the evaluations in each task are captured separately. | ||
with sentry_sdk.isolation_scope(): | ||
add_feature_flag(flag_key, False) | ||
# use a tag to identify to identify events later on | ||
sentry_sdk.set_tag("task_id", flag_key) | ||
sentry_sdk.capture_exception(Exception("something wrong!")) | ||
|
||
async def runner(): | ||
return asyncio.gather(task("world"), task("other")) | ||
|
||
asyncio.run(runner()) | ||
|
||
# Capture error in original scope | ||
sentry_sdk.set_tag("task_id", "0") | ||
sentry_sdk.capture_exception(Exception("something wrong!")) | ||
|
||
assert len(events) == 3 | ||
events.sort(key=lambda e: e["tags"]["task_id"]) | ||
|
||
assert events[0]["contexts"]["flags"] == { | ||
"values": [ | ||
{"flag": "hello", "result": False}, | ||
] | ||
} | ||
assert events[1]["contexts"]["flags"] == { | ||
"values": [ | ||
{"flag": "hello", "result": False}, | ||
{"flag": "other", "result": False}, | ||
] | ||
} | ||
assert events[2]["contexts"]["flags"] == { | ||
"values": [ | ||
{"flag": "hello", "result": False}, | ||
{"flag": "world", "result": False}, | ||
] | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.