Coverage for benefits / sentry.py: 80%

56 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-13 19:35 +0000

1import datetime 

2import os 

3import shutil 

4import subprocess 

5 

6import sentry_sdk 

7from django.conf import settings 

8from sentry_sdk.integrations.django import DjangoIntegration 

9from sentry_sdk.scrubber import DEFAULT_DENYLIST, EventScrubber 

10 

11from benefits import VERSION 

12 

13SENTRY_CSP_REPORT_URI = None 

14 

15 

16def git_available(): 

17 return bool(shutil.which("git")) 

18 

19 

20# https://stackoverflow.com/a/24584384/358804 

21def is_git_directory(path="."): 

22 with open(os.devnull, "w") as dev_null: 

23 return subprocess.call(["git", "-C", path, "status"], stderr=dev_null, stdout=dev_null) == 0 

24 

25 

26# https://stackoverflow.com/a/21901260/358804 

27def get_git_revision_hash(): 

28 return subprocess.check_output(["git", "rev-parse", "HEAD"]).decode("ascii").strip() 

29 

30 

31def get_sha_file_path(): 

32 current_file = os.path.dirname(os.path.abspath(__file__)) 

33 return os.path.join(current_file, "..", "static", "sha.txt") 

34 

35 

36def get_sha_from_file(): 

37 sha_path = get_sha_file_path() 

38 if os.path.isfile(sha_path): 

39 with open(sha_path) as f: 

40 return f.read().strip() 

41 else: 

42 return None 

43 

44 

45def get_release() -> str: 

46 """Returns the first available: the SHA from Git, the value from sha.txt, or the VERSION.""" 

47 

48 if git_available() and is_git_directory(): 

49 return get_git_revision_hash() 

50 else: 

51 sha = get_sha_from_file() 

52 if sha: 

53 return sha 

54 else: 

55 # one of the above *should* always be available, but including this just in case 

56 return VERSION 

57 

58 

59def get_denylist(): 

60 # custom denylist 

61 denylist = DEFAULT_DENYLIST + ["sub", "name"] 

62 return denylist 

63 

64 

65def get_traces_sample_rate(): 

66 try: 

67 rate = float(os.environ.get("SENTRY_TRACES_SAMPLE_RATE", "0.0")) 

68 if rate < 0.0 or rate > 1.0: 

69 print( 

70 f"[{datetime.datetime.now().strftime("%d/%b/%Y %H:%M:%S")}] " 

71 "WARNING benefits.sentry SENTRY_TRACES_SAMPLE_RATE was not in the range [0.0, 1.0], defaulting to 0.0" 

72 ) 

73 rate = 0.0 

74 else: 

75 print( 

76 f"[{datetime.datetime.now().strftime("%d/%b/%Y %H:%M:%S")}] " 

77 f"INFO benefits.sentry SENTRY_TRACES_SAMPLE_RATE set to: {rate}" 

78 ) 

79 except ValueError: 

80 print( 

81 f"[{datetime.datetime.now().strftime("%d/%b/%Y %H:%M:%S")}] " 

82 "WARNING benefits.sentry SENTRY_TRACES_SAMPLE_RATE did not parse to float, defaulting to 0.0" 

83 ) 

84 rate = 0.0 

85 

86 return rate 

87 

88 

89def configure(): 

90 sentry_dsn = os.environ.get("SENTRY_DSN") 

91 sentry_environment = os.environ.get("SENTRY_ENVIRONMENT", settings.RUNTIME_ENVIRONMENT()) 

92 

93 if sentry_dsn: 93 ↛ 94line 93 didn't jump to line 94 because the condition on line 93 was never true

94 release = get_release() 

95 print( 

96 f"[{datetime.datetime.now().strftime("%d/%b/%Y %H:%M:%S")}] " 

97 f"INFO benefits.sentry Enabling Sentry for environment '{sentry_environment}', " 

98 f"release '{release}'..." 

99 ) 

100 

101 # https://docs.sentry.io/platforms/python/configuration/ 

102 sentry_sdk.init( 

103 dsn=sentry_dsn, 

104 integrations=[ 

105 DjangoIntegration(), 

106 ], 

107 traces_sample_rate=get_traces_sample_rate(), 

108 environment=sentry_environment, 

109 release=release, 

110 in_app_include=["benefits"], 

111 # send_default_pii must be False (the default) for a custom EventScrubber/denylist 

112 # https://docs.sentry.io/platforms/python/data-management/sensitive-data/#event_scrubber 

113 send_default_pii=False, 

114 event_scrubber=EventScrubber(denylist=get_denylist(), recursive=True), 

115 ) 

116 

117 # override the module-level variable when configuration happens, if set 

118 global SENTRY_CSP_REPORT_URI 

119 SENTRY_CSP_REPORT_URI = os.environ.get("SENTRY_REPORT_URI", "") 

120 else: 

121 print( 

122 f"[{datetime.datetime.now().strftime("%d/%b/%Y %H:%M:%S")}] " 

123 "INFO benefits.sentry SENTRY_DSN not set, so won't send events" 

124 )