mirror of
https://github.com/Sea-Haven-Industries/procurement-ingest.git
synced 2026-09-30 07:13:13 +00:00
* feat(lambda): report unhandled Lambda errors to Sentry * fix(lambda): strip Sentry stack-frame locals * style(tests): wrap long lines in sentry_init tests
134 lines
3.6 KiB
Python
134 lines
3.6 KiB
Python
"""Shared Sentry SDK init for every procurement-ingest Lambda.
|
|
|
|
Imported for side effect from each handler. ``init_sentry()`` is a no-op when
|
|
``SENTRY_DSN`` is unset so pytest, local invokes, and a missing HCP var never
|
|
talk to Sentry. ``before_send`` strips auth headers, drops request/extra keys
|
|
that can hold MIME bodies, Bedrock prompts, or HMAC secret material, and
|
|
removes exception stack-frame locals. ``include_local_variables=False`` keeps
|
|
those locals out of the event in the first place.
|
|
"""
|
|
|
|
import os
|
|
|
|
import sentry_sdk
|
|
from sentry_sdk.integrations.aws_lambda import AwsLambdaIntegration
|
|
|
|
_HEADER_DROP_NAMES = frozenset(
|
|
{
|
|
"authorization",
|
|
"x-auth-token",
|
|
"cookie",
|
|
"x-amz-security-token",
|
|
}
|
|
)
|
|
_DROP_REQUEST_KEYS = frozenset(
|
|
{
|
|
"body",
|
|
"Body",
|
|
"data",
|
|
"cookies",
|
|
"raw_email",
|
|
"prompt",
|
|
"secret",
|
|
"SecretString",
|
|
"hmac",
|
|
"keys",
|
|
}
|
|
)
|
|
_DROP_EXTRA_NEEDLES = (
|
|
"body",
|
|
"email",
|
|
"prompt",
|
|
"secret",
|
|
"hmac",
|
|
"token",
|
|
"mime",
|
|
"raw_email",
|
|
)
|
|
|
|
|
|
def _drop_header(name):
|
|
lower = str(name).lower()
|
|
return lower in _HEADER_DROP_NAMES or lower.startswith("x-amz-")
|
|
|
|
|
|
def _scrub_headers(headers):
|
|
if isinstance(headers, dict):
|
|
return {k: v for k, v in headers.items() if not _drop_header(k)}
|
|
if isinstance(headers, list):
|
|
kept = []
|
|
for pair in headers:
|
|
if isinstance(pair, (list, tuple)) and pair and _drop_header(pair[0]):
|
|
continue
|
|
kept.append(pair)
|
|
return kept
|
|
return headers
|
|
|
|
|
|
def _stacktraces(event):
|
|
traces = []
|
|
stacktrace = event.get("stacktrace")
|
|
if isinstance(stacktrace, dict):
|
|
traces.append(stacktrace)
|
|
for section in ("exception", "threads"):
|
|
container = event.get(section)
|
|
if not isinstance(container, dict):
|
|
continue
|
|
values = container.get("values")
|
|
if not isinstance(values, list):
|
|
continue
|
|
for item in values:
|
|
if not isinstance(item, dict):
|
|
continue
|
|
inner = item.get("stacktrace")
|
|
if isinstance(inner, dict):
|
|
traces.append(inner)
|
|
return traces
|
|
|
|
|
|
def _strip_stack_locals(event):
|
|
"""Drop frame locals. Names like ``raw``/``item`` still hold MIME or secrets."""
|
|
for stacktrace in _stacktraces(event):
|
|
frames = stacktrace.get("frames")
|
|
if not isinstance(frames, list):
|
|
continue
|
|
for frame in frames:
|
|
if isinstance(frame, dict):
|
|
frame.pop("vars", None)
|
|
|
|
|
|
def _before_send(event, _hint):
|
|
request = event.get("request")
|
|
if isinstance(request, dict):
|
|
headers = request.get("headers")
|
|
if headers is not None:
|
|
request["headers"] = _scrub_headers(headers)
|
|
for key in list(request):
|
|
if key in _DROP_REQUEST_KEYS or str(key).lower() in {"body", "data"}:
|
|
request.pop(key, None)
|
|
extra = event.get("extra")
|
|
if isinstance(extra, dict):
|
|
for key in list(extra):
|
|
lower = str(key).lower()
|
|
if any(needle in lower for needle in _DROP_EXTRA_NEEDLES):
|
|
extra.pop(key, None)
|
|
_strip_stack_locals(event)
|
|
return event
|
|
|
|
|
|
def init_sentry():
|
|
dsn = os.environ.get("SENTRY_DSN")
|
|
if not dsn:
|
|
return
|
|
sentry_sdk.init(
|
|
dsn=dsn,
|
|
integrations=[AwsLambdaIntegration(timeout_warning=True)],
|
|
send_default_pii=False,
|
|
include_local_variables=False,
|
|
enable_logs=False,
|
|
traces_sample_rate=0.0,
|
|
before_send=_before_send,
|
|
)
|
|
|
|
|
|
init_sentry()
|