Skip to content

Commit 288f69a

Browse files
authored
Moved adding of flags context into Scope (#3917)
Using an error_processor to read data from the scope to add to the event is an anti-pattern. Moving this into `Scope.apply_to_event()`. This PR: - moves code that adds flags to an event from an error processor into the `Scope` class - moves `add_feature_flag()` function from `sentry_sdk.integrations.feature_flags` into `sentry_sdk.feature_flags`
1 parent 9f9ff34 commit 288f69a

File tree

9 files changed

+74
-119
lines changed

9 files changed

+74
-119
lines changed

sentry_sdk/flag_utils.py renamed to sentry_sdk/feature_flags.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
from typing import TYPE_CHECKING
2-
31
import sentry_sdk
42
from sentry_sdk._lru_cache import LRUCache
53

4+
from typing import TYPE_CHECKING
5+
66
if TYPE_CHECKING:
7-
from typing import TypedDict, Optional
8-
from sentry_sdk._types import Event, ExcInfo
7+
from typing import TypedDict
98

109
FlagData = TypedDict("FlagData", {"flag": str, "result": bool})
1110

@@ -33,8 +32,11 @@ def set(self, flag, result):
3332
self.buffer.set(flag, result)
3433

3534

36-
def flag_error_processor(event, exc_info):
37-
# type: (Event, ExcInfo) -> Optional[Event]
38-
scope = sentry_sdk.get_current_scope()
39-
event["contexts"]["flags"] = {"values": scope.flags.get()}
40-
return event
35+
def add_feature_flag(flag, result):
36+
# type: (str, bool) -> None
37+
"""
38+
Records a flag and its value to be sent on subsequent error events.
39+
We recommend you do this on flag evaluations. Flags are buffered per Sentry scope.
40+
"""
41+
flags = sentry_sdk.get_current_scope().flags
42+
flags.set(flag, result)

sentry_sdk/integrations/feature_flags.py

Lines changed: 0 additions & 44 deletions
This file was deleted.

sentry_sdk/integrations/launchdarkly.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import sentry_sdk
33

44
from sentry_sdk.integrations import DidNotEnable, Integration
5-
from sentry_sdk.flag_utils import flag_error_processor
65

76
try:
87
import ldclient
@@ -41,8 +40,7 @@ def __init__(self, ld_client=None):
4140
@staticmethod
4241
def setup_once():
4342
# type: () -> None
44-
scope = sentry_sdk.get_current_scope()
45-
scope.add_error_processor(flag_error_processor)
43+
pass
4644

4745

4846
class LaunchDarklyHook(Hook):

sentry_sdk/integrations/openfeature.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import sentry_sdk
33

44
from sentry_sdk.integrations import DidNotEnable, Integration
5-
from sentry_sdk.flag_utils import flag_error_processor
65

76
try:
87
from openfeature import api
@@ -21,9 +20,6 @@ class OpenFeatureIntegration(Integration):
2120
@staticmethod
2221
def setup_once():
2322
# type: () -> None
24-
scope = sentry_sdk.get_current_scope()
25-
scope.add_error_processor(flag_error_processor)
26-
2723
# Register the hook within the global openfeature hooks list.
2824
api.add_hooks(hooks=[OpenFeatureHook()])
2925

sentry_sdk/integrations/unleash.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
from typing import Any
33

44
import sentry_sdk
5-
from sentry_sdk.flag_utils import flag_error_processor
65
from sentry_sdk.integrations import Integration, DidNotEnable
76

87
try:
@@ -49,7 +48,3 @@ def sentry_get_variant(self, feature, *args, **kwargs):
4948

5049
UnleashClient.is_enabled = sentry_is_enabled # type: ignore
5150
UnleashClient.get_variant = sentry_get_variant # type: ignore
52-
53-
# Error processor
54-
scope = sentry_sdk.get_current_scope()
55-
scope.add_error_processor(flag_error_processor)

sentry_sdk/scope.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111

1212
from sentry_sdk.attachments import Attachment
1313
from sentry_sdk.consts import DEFAULT_MAX_BREADCRUMBS, FALSE_VALUES, INSTRUMENTER
14-
from sentry_sdk.flag_utils import FlagBuffer, DEFAULT_FLAG_CAPACITY
14+
from sentry_sdk.feature_flags import FlagBuffer, DEFAULT_FLAG_CAPACITY
1515
from sentry_sdk.profiler.continuous_profiler import try_autostart_continuous_profiler
1616
from sentry_sdk.profiler.transaction_profiler import Profile
1717
from sentry_sdk.session import Session
@@ -1378,6 +1378,14 @@ def _apply_contexts_to_event(self, event, hint, options):
13781378
else:
13791379
contexts["trace"] = self.get_trace_context()
13801380

1381+
def _apply_flags_to_event(self, event, hint, options):
1382+
# type: (Event, Hint, Optional[Dict[str, Any]]) -> None
1383+
flags = self.flags.get()
1384+
if len(flags) > 0:
1385+
event.setdefault("contexts", {}).setdefault("flags", {}).update(
1386+
{"values": flags}
1387+
)
1388+
13811389
def _drop(self, cause, ty):
13821390
# type: (Any, str) -> Optional[Any]
13831391
logger.info("%s (%s) dropped event", ty, cause)
@@ -1476,6 +1484,7 @@ def apply_to_event(
14761484

14771485
if not is_transaction and not is_check_in:
14781486
self._apply_breadcrumbs_to_event(event, hint, options)
1487+
self._apply_flags_to_event(event, hint, options)
14791488

14801489
event = self.run_error_processors(event, hint)
14811490
if event is None:
@@ -1518,6 +1527,12 @@ def update_from_scope(self, scope):
15181527
self._propagation_context = scope._propagation_context
15191528
if scope._session:
15201529
self._session = scope._session
1530+
if scope._flags:
1531+
if not self._flags:
1532+
self._flags = deepcopy(scope._flags)
1533+
else:
1534+
for flag in scope._flags.get():
1535+
self._flags.set(flag["flag"], flag["result"])
15211536

15221537
def update_from_kwargs(
15231538
self,

tests/integrations/feature_flags/__init__.py

Whitespace-only changes.

tests/integrations/feature_flags/test_feature_flags.py renamed to tests/test_feature_flags.py

Lines changed: 46 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,11 @@
44
import pytest
55

66
import sentry_sdk
7-
from sentry_sdk.integrations.feature_flags import (
8-
FeatureFlagsIntegration,
9-
add_feature_flag,
10-
)
7+
from sentry_sdk.feature_flags import add_feature_flag, FlagBuffer
118

129

1310
def test_featureflags_integration(sentry_init, capture_events, uninstall_integration):
14-
uninstall_integration(FeatureFlagsIntegration.identifier)
15-
sentry_init(integrations=[FeatureFlagsIntegration()])
11+
sentry_init()
1612

1713
add_feature_flag("hello", False)
1814
add_feature_flag("world", True)
@@ -34,8 +30,7 @@ def test_featureflags_integration(sentry_init, capture_events, uninstall_integra
3430
def test_featureflags_integration_threaded(
3531
sentry_init, capture_events, uninstall_integration
3632
):
37-
uninstall_integration(FeatureFlagsIntegration.identifier)
38-
sentry_init(integrations=[FeatureFlagsIntegration()])
33+
sentry_init()
3934
events = capture_events()
4035

4136
# Capture an eval before we split isolation scopes.
@@ -86,8 +81,7 @@ def test_featureflags_integration_asyncio(
8681
):
8782
asyncio = pytest.importorskip("asyncio")
8883

89-
uninstall_integration(FeatureFlagsIntegration.identifier)
90-
sentry_init(integrations=[FeatureFlagsIntegration()])
84+
sentry_init()
9185
events = capture_events()
9286

9387
# Capture an eval before we split isolation scopes.
@@ -131,3 +125,45 @@ async def runner():
131125
{"flag": "world", "result": False},
132126
]
133127
}
128+
129+
130+
def test_flag_tracking():
131+
"""Assert the ring buffer works."""
132+
buffer = FlagBuffer(capacity=3)
133+
buffer.set("a", True)
134+
flags = buffer.get()
135+
assert len(flags) == 1
136+
assert flags == [{"flag": "a", "result": True}]
137+
138+
buffer.set("b", True)
139+
flags = buffer.get()
140+
assert len(flags) == 2
141+
assert flags == [{"flag": "a", "result": True}, {"flag": "b", "result": True}]
142+
143+
buffer.set("c", True)
144+
flags = buffer.get()
145+
assert len(flags) == 3
146+
assert flags == [
147+
{"flag": "a", "result": True},
148+
{"flag": "b", "result": True},
149+
{"flag": "c", "result": True},
150+
]
151+
152+
buffer.set("d", False)
153+
flags = buffer.get()
154+
assert len(flags) == 3
155+
assert flags == [
156+
{"flag": "b", "result": True},
157+
{"flag": "c", "result": True},
158+
{"flag": "d", "result": False},
159+
]
160+
161+
buffer.set("e", False)
162+
buffer.set("f", False)
163+
flags = buffer.get()
164+
assert len(flags) == 3
165+
assert flags == [
166+
{"flag": "d", "result": False},
167+
{"flag": "e", "result": False},
168+
{"flag": "f", "result": False},
169+
]

tests/test_flag_utils.py

Lines changed: 0 additions & 43 deletions
This file was deleted.

0 commit comments

Comments
 (0)