feature_flags.py 2.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. import copy
  2. import sentry_sdk
  3. from sentry_sdk._lru_cache import LRUCache
  4. from threading import Lock
  5. from typing import TYPE_CHECKING, Any
  6. if TYPE_CHECKING:
  7. from typing import TypedDict
  8. FlagData = TypedDict("FlagData", {"flag": str, "result": bool})
  9. DEFAULT_FLAG_CAPACITY = 100
  10. class FlagBuffer:
  11. def __init__(self, capacity: int) -> None:
  12. self.capacity = capacity
  13. self.lock = Lock()
  14. # Buffer is private. The name is mangled to discourage use. If you use this attribute
  15. # directly you're on your own!
  16. self.__buffer = LRUCache(capacity)
  17. def clear(self) -> None:
  18. self.__buffer = LRUCache(self.capacity)
  19. def __deepcopy__(self, memo: "dict[int, Any]") -> "FlagBuffer":
  20. with self.lock:
  21. buffer = FlagBuffer(self.capacity)
  22. buffer.__buffer = copy.deepcopy(self.__buffer, memo)
  23. return buffer
  24. def get(self) -> "list[FlagData]":
  25. with self.lock:
  26. return [
  27. {"flag": key, "result": value} for key, value in self.__buffer.get_all()
  28. ]
  29. def set(self, flag: str, result: bool) -> None:
  30. if isinstance(result, FlagBuffer):
  31. # If someone were to insert `self` into `self` this would create a circular dependency
  32. # on the lock. This is of course a deadlock. However, this is far outside the expected
  33. # usage of this class. We guard against it here for completeness and to document this
  34. # expected failure mode.
  35. raise ValueError(
  36. "FlagBuffer instances can not be inserted into the dictionary."
  37. )
  38. with self.lock:
  39. self.__buffer.set(flag, result)
  40. def add_feature_flag(flag: str, result: bool) -> None:
  41. """
  42. Records a flag and its value to be sent on subsequent error events.
  43. We recommend you do this on flag evaluations. Flags are buffered per Sentry scope.
  44. """
  45. flags = sentry_sdk.get_isolation_scope().flags
  46. flags.set(flag, result)
  47. span = sentry_sdk.get_current_span()
  48. if span:
  49. span.set_flag(f"flag.evaluation.{flag}", result)