Coverage for benefits/sentry.py: 81%
57 statements
« prev ^ index » next coverage.py v7.6.7, created at 2024-11-22 18:00 +0000
« prev ^ index » next coverage.py v7.6.7, created at 2024-11-22 18:00 +0000
1import logging
2import shutil
3import os
4import subprocess
6from django.conf import settings
7import sentry_sdk
8from sentry_sdk.integrations.django import DjangoIntegration
9from sentry_sdk.scrubber import EventScrubber, DEFAULT_DENYLIST
11from benefits import VERSION
13logger = logging.getLogger(__name__)
15SENTRY_CSP_REPORT_URI = None
18def git_available():
19 return bool(shutil.which("git"))
22# https://stackoverflow.com/a/24584384/358804
23def is_git_directory(path="."):
24 with open(os.devnull, "w") as dev_null:
25 return subprocess.call(["git", "-C", path, "status"], stderr=dev_null, stdout=dev_null) == 0
28# https://stackoverflow.com/a/21901260/358804
29def get_git_revision_hash():
30 return subprocess.check_output(["git", "rev-parse", "HEAD"]).decode("ascii").strip()
33def get_sha_file_path():
34 current_file = os.path.dirname(os.path.abspath(__file__))
35 return os.path.join(current_file, "..", "static", "sha.txt")
38def get_sha_from_file():
39 sha_path = get_sha_file_path()
40 if os.path.isfile(sha_path):
41 with open(sha_path) as f:
42 return f.read().strip()
43 else:
44 return None
47def get_release() -> str:
48 """Returns the first available: the SHA from Git, the value from sha.txt, or the VERSION."""
50 if git_available() and is_git_directory():
51 return get_git_revision_hash()
52 else:
53 sha = get_sha_from_file()
54 if sha:
55 return sha
56 else:
57 # one of the above *should* always be available, but including this just in case
58 return VERSION
61def get_denylist():
62 # custom denylist
63 denylist = DEFAULT_DENYLIST + ["sub", "name"]
64 return denylist
67def get_traces_sample_rate():
68 try:
69 rate = float(os.environ.get("SENTRY_TRACES_SAMPLE_RATE", "0.0"))
70 if rate < 0.0 or rate > 1.0:
71 logger.warning("SENTRY_TRACES_SAMPLE_RATE was not in the range [0.0, 1.0], defaulting to 0.0")
72 rate = 0.0
73 else:
74 logger.info(f"SENTRY_TRACES_SAMPLE_RATE set to: {rate}")
75 except ValueError:
76 logger.warning("SENTRY_TRACES_SAMPLE_RATE did not parse to float, defaulting to 0.0")
77 rate = 0.0
79 return rate
82def configure():
83 sentry_dsn = os.environ.get("SENTRY_DSN")
84 sentry_environment = os.environ.get("SENTRY_ENVIRONMENT", settings.RUNTIME_ENVIRONMENT())
86 if sentry_dsn: 86 ↛ 87line 86 didn't jump to line 87 because the condition on line 86 was never true
87 release = get_release()
88 logger.info(f"Enabling Sentry for environment '{sentry_environment}', release '{release}'...")
90 # https://docs.sentry.io/platforms/python/configuration/
91 sentry_sdk.init(
92 dsn=sentry_dsn,
93 integrations=[
94 DjangoIntegration(),
95 ],
96 traces_sample_rate=get_traces_sample_rate(),
97 environment=sentry_environment,
98 release=release,
99 in_app_include=["benefits"],
100 # send_default_pii must be False (the default) for a custom EventScrubber/denylist
101 # https://docs.sentry.io/platforms/python/data-management/sensitive-data/#event_scrubber
102 send_default_pii=False,
103 event_scrubber=EventScrubber(denylist=get_denylist(), recursive=True),
104 )
106 # override the module-level variable when configuration happens, if set
107 global SENTRY_CSP_REPORT_URI
108 SENTRY_CSP_REPORT_URI = os.environ.get("SENTRY_REPORT_URI", "")
109 else:
110 logger.info("SENTRY_DSN not set, so won't send events")