feature_flags.py 2.2 KB

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