Coverage for benefits / sentry.py: 80%
56 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-13 19:35 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-02-13 19:35 +0000
1import datetime
2import os
3import shutil
4import subprocess
6import sentry_sdk
7from django.conf import settings
8from sentry_sdk.integrations.django import DjangoIntegration
9from sentry_sdk.scrubber import DEFAULT_DENYLIST, EventScrubber
11from benefits import VERSION
13SENTRY_CSP_REPORT_URI = None
16def git_available():
17 return bool(shutil.which("git"))
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
26# https://stackoverflow.com/a/21901260/358804
27def get_git_revision_hash():
28 return subprocess.check_output(["git", "rev-parse", "HEAD"]).decode("ascii").strip()
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")
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
45def get_release() -> str:
46 """Returns the first available: the SHA from Git, the value from sha.txt, or the VERSION."""
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
59def get_denylist():
60 # custom denylist
61 denylist = DEFAULT_DENYLIST + ["sub", "name"]
62 return denylist
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
86 return rate
89def configure():
90 sentry_dsn = os.environ.get("SENTRY_DSN")
91 sentry_environment = os.environ.get("SENTRY_ENVIRONMENT", settings.RUNTIME_ENVIRONMENT())
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 )
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 )
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 )