"""Sentry Client. Provides sentry_client for ad hoc capturing of messages and errors. https://sentry.io/for/flask/ """ from typing import Any from sentry_scrubber import SentryScrubber import sentry_sdk from sentry_sdk import configure_scope from sentry_sdk.integrations.flask import FlaskIntegration from sentry_sdk.integrations.sqlalchemy import SqlalchemyIntegration from sentry_sdk.scrubber import DEFAULT_DENYLIST, EventScrubber from payee.constants.constants import SENSITIVE_FIELDS sentry_client = None scrubber = None def setup_sentry(config) -> None: """Init Sentry with provided DSN url.""" global sentry_client, scrubber if not config.SENTRY: sentry_client = None scrubber = None return # Create scrubber instance only when Sentry is actually being initialized scrubber = SentryScrubber(sensitive_fields=SENSITIVE_FIELDS) sentry_sdk.init( dsn=config.SENTRY, environment=config.SENTRY_ENVIRONMENT, integrations=[ FlaskIntegration(transaction_style='url'), SqlalchemyIntegration(), ], # before_send is required only for plain text errors that appear in Breadcrumbs (and possibly in other locations) # in fact, this is an intensive operation since it recursively traverses the entire event # there is some room for optimization if we find that performance is impacted before_send=scrubber.before_send_handler, send_default_pii=False, event_scrubber=EventScrubber( denylist=DEFAULT_DENYLIST + SENSITIVE_FIELDS, recursive=True ), attach_stacktrace=True, max_value_length=2048, ) sentry_client = sentry_sdk.client def send_to_sentry(message: str, errors: Any, status: int, sentry_message: str) -> None: """Send to Sentry with scrubbed sensitive data.""" if not sentry_client or not scrubber: return with configure_scope() as scope: scope.set_extra('message', scrubber.scrub_data(message)) scope.set_extra('errors', scrubber.scrub_data(errors)) scope.set_extra('status', status) sentry_sdk.capture_message(sentry_message)