Skip to content

Commit

Permalink
Illustrate moving the flags from the scope to the integration
Browse files Browse the repository at this point in the history
  • Loading branch information
antonpirker committed Oct 24, 2024
1 parent b1a0973 commit 430387f
Show file tree
Hide file tree
Showing 4 changed files with 104 additions and 29 deletions.
2 changes: 1 addition & 1 deletion sentry_sdk/flag_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
FlagData = TypedDict("FlagData", {"flag": str, "result": bool})


DEFAULT_FLAG_CAPACITY = 100
# DEFAULT_FLAG_CAPACITY = 100


class FlagBuffer:
Expand Down
49 changes: 35 additions & 14 deletions sentry_sdk/integrations/openfeature.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from typing import TYPE_CHECKING
import sentry_sdk

from sentry_sdk.flag_utils import FlagBuffer
from sentry_sdk.integrations import DidNotEnable, Integration

try:
Expand All @@ -16,35 +17,55 @@
raise DidNotEnable("OpenFeature is not installed")


DEFAULT_FLAG_CAPACITY = 100


class OpenFeatureIntegration(Integration):
identifier = "openfeature"

def __init__(self, max_flags=DEFAULT_FLAG_CAPACITY):
# type: (OpenFeatureIntegration, int) -> None
self._max_flags = max_flags
self._flags = None # type: Optional[FlagBuffer]

@staticmethod
def setup_once():
# type: () -> None
def error_processor(event, exc_info):
# type: (Event, ExcInfo) -> Optional[Event]
scope = sentry_sdk.get_current_scope()
event["contexts"]["flags"] = {"values": scope.flags.get()}
return event

scope = sentry_sdk.get_current_scope()
scope.add_error_processor(error_processor)

# Register the hook within the global openfeature hooks list.
api.add_hooks(hooks=[OpenFeatureHook()])

@property
def flags(self):
# type: () -> FlagBuffer
if self._flags is None:
max_flags = self._max_flags or DEFAULT_FLAG_CAPACITY
self._flags = FlagBuffer(capacity=max_flags)
return self._flags

class OpenFeatureHook(Hook):

class OpenFeatureHook(Hook):
def after(self, hook_context, details, hints):
# type: (HookContext, FlagEvaluationDetails[bool], HookHints) -> None
integration = sentry_sdk.get_client().get_integration(OpenFeatureIntegration)
if integration is None:
return

if isinstance(details.value, bool):
flags = sentry_sdk.get_current_scope().flags
flags.set(details.flag_key, details.value)
integration.flags.set(details.flag_key, details.value)

def error(self, hook_context, exception, hints):
# type: (HookContext, Exception, HookHints) -> None
integration = sentry_sdk.get_client().get_integration(OpenFeatureIntegration)
if integration is None:
return

def error_processor(event, exc_info):
# type: (Event, ExcInfo) -> Optional[Event]
event["contexts"]["flags"] = {"values": integration.flags.get()}
return event

scope = sentry_sdk.get_current_scope()
scope.add_error_processor(error_processor)

if isinstance(hook_context.default_value, bool):
flags = sentry_sdk.get_current_scope().flags
flags.set(hook_context.flag_key, hook_context.default_value)
integration.flags.set(hook_context.flag_key, hook_context.default_value)
29 changes: 15 additions & 14 deletions sentry_sdk/scope.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

from sentry_sdk.attachments import Attachment
from sentry_sdk.consts import DEFAULT_MAX_BREADCRUMBS, FALSE_VALUES, INSTRUMENTER
from sentry_sdk.flag_utils import FlagBuffer, DEFAULT_FLAG_CAPACITY

# from sentry_sdk.flag_utils import FlagBuffer, DEFAULT_FLAG_CAPACITY
from sentry_sdk.profiler.continuous_profiler import try_autostart_continuous_profiler
from sentry_sdk.profiler.transaction_profiler import Profile
from sentry_sdk.session import Session
Expand Down Expand Up @@ -193,7 +194,7 @@ class Scope:
"client",
"_type",
"_last_event_id",
"_flags",
# "_flags",
)

def __init__(self, ty=None, client=None):
Expand Down Expand Up @@ -251,7 +252,7 @@ def __copy__(self):

rv._last_event_id = self._last_event_id

rv._flags = copy(self._flags)
# rv._flags = copy(self._flags)

return rv

Expand Down Expand Up @@ -689,7 +690,7 @@ def clear(self):

# self._last_event_id is only applicable to isolation scopes
self._last_event_id = None # type: Optional[str]
self._flags = None # type: Optional[FlagBuffer]
# self._flags = None # type: Optional[FlagBuffer]

@_attr_setter
def level(self, value):
Expand Down Expand Up @@ -1551,16 +1552,16 @@ def __repr__(self):
self._type,
)

@property
def flags(self):
# type: () -> FlagBuffer
if self._flags is None:
max_flags = (
self.get_client().options["_experiments"].get("max_flags")
or DEFAULT_FLAG_CAPACITY
)
self._flags = FlagBuffer(capacity=max_flags)
return self._flags
# @property
# def flags(self):
# # type: () -> FlagBuffer
# if self._flags is None:
# max_flags = (
# self.get_client().options["_experiments"].get("max_flags")
# or DEFAULT_FLAG_CAPACITY
# )
# self._flags = FlagBuffer(capacity=max_flags)
# return self._flags


@contextmanager
Expand Down
53 changes: 53 additions & 0 deletions tests/integrations/openfeature/test_openfeature.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,65 @@
import asyncio
import concurrent.futures as cf

import sentry_sdk

from openfeature import api
from openfeature.provider.in_memory_provider import InMemoryFlag, InMemoryProvider
from sentry_sdk.integrations.openfeature import OpenFeatureIntegration


def test_openfeature_integration_flags_on_integration(sentry_init, capture_events):
sentry_init(integrations=[OpenFeatureIntegration()])

flags = {
"hello": InMemoryFlag("on", {"on": True, "off": False}),
"world": InMemoryFlag("off", {"on": True, "off": False}),
}
api.set_provider(InMemoryProvider(flags))

client = api.get_client()
client.get_boolean_value("hello", default_value=False)
client.get_boolean_value("world", default_value=False)
client.get_boolean_value("other", default_value=True)

events = capture_events()

sentry_sdk.capture_exception(Exception("test"))

(event,) = events

assert event["contexts"]["flags"]["values"] == [
{"flag": "hello", "result": True},
{"flag": "world", "result": False},
{"flag": "other", "result": True},
]


def test_openfeature_integration_max_flags(sentry_init, capture_events):
sentry_init(integrations=[OpenFeatureIntegration(max_flags=2)])

flags = {
"hello": InMemoryFlag("on", {"on": True, "off": False}),
"world": InMemoryFlag("off", {"on": True, "off": False}),
}
api.set_provider(InMemoryProvider(flags))

client = api.get_client()
client.get_boolean_value("hello", default_value=False)
client.get_boolean_value("world", default_value=False)
client.get_boolean_value("other", default_value=True)

events = capture_events()

sentry_sdk.capture_exception(Exception("test"))

(event,) = events
assert event["contexts"]["flags"]["values"] == [
{"flag": "world", "result": False},
{"flag": "other", "result": True},
]


def test_openfeature_integration(sentry_init):
sentry_init(integrations=[OpenFeatureIntegration()])

Expand Down

0 comments on commit 430387f

Please sign in to comment.