gendesign/tradein-mvp/backend/app/observability/sentry_scrub.py
Light1YT ac533812c2 feat(tradein): enrich GlitchTip SDK init with integrations + PII scrub (#396)
The tradein GlitchTip init was barebones (dsn/environment/traces/pii only) —
no integrations, so FastAPI request errors and logged ERRORs were not
auto-captured. Enrich it so real error monitoring works once GLITCHTIP_DSN
is set:
- integrations: Starlette, FastApi (request errors), SQLAlchemy, Httpx
  (breadcrumbs), Logging(level=INFO, event_level=ERROR → logger.error events)
- release tag (GIT_SHA / SENTRY_RELEASE / "unknown")
- before_send=scrub_pii_event — redacts consumer PII (client_name/phone/email)
  from error event bodies (request.data / extra / contexts), beyond what
  send_default_pii=False covers
- NO CeleryIntegration (prod runs no celery worker; in-app scheduler)

Guard preserved: full no-op while GLITCHTIP_DSN is empty (current prod) →
safe to deploy now; activates when an operator sets the DSN.

Closes #396
2026-05-29 14:22:07 +05:00

44 lines
1.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Хук before_send для GlitchTip/Sentry SDK (tradein-local, #396).
Redact-ит consumer-PII (client_name / client_phone / client_email и пр.)
из error events до отправки в GlitchTip — estimator/trade-in flow таскает
эти поля, а send_default_pii=False их не покрывает (это user-data в
request.data / extra / contexts, не PII-заголовки).
"""
from __future__ import annotations
from typing import Any
from sentry_sdk.types import Event
_REDACTED = "[REDACTED]"
# Ключи consumer-PII (нижний регистр; сверка case-insensitive).
_PII_KEYS = frozenset(
{"client_name", "client_phone", "client_email", "phone", "email", "name"}
)
def _scrub(obj: Any) -> None:
"""Рекурсивно заменить значения PII-ключей в dict на [REDACTED] (in-place)."""
if isinstance(obj, dict):
for key, value in obj.items():
if isinstance(key, str) and key.lower() in _PII_KEYS:
obj[key] = _REDACTED
else:
_scrub(value)
elif isinstance(obj, list):
for item in obj:
_scrub(item)
def scrub_pii_event(event: Event, _hint: dict[str, Any]) -> Event | None:
"""Redact consumer-PII из error event перед отправкой. Возвращает event (не None)."""
if not isinstance(event, dict):
return event
request = event.get("request")
if isinstance(request, dict):
_scrub(request.get("data"))
_scrub(event.get("extra"))
_scrub(event.get("contexts"))
return event